diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..bb64a822 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = Tigramite +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle new file mode 100644 index 00000000..633941fd Binary files /dev/null and b/docs/_build/doctrees/environment.pickle differ diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree new file mode 100644 index 00000000..dfd37568 Binary files /dev/null and b/docs/_build/doctrees/index.doctree differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo new file mode 100644 index 00000000..c906bff8 --- /dev/null +++ b/docs/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 43e03fac560e16bfeebe8fc7915cb678 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/.nojekyll b/docs/_build/html/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/docs/_build/html/_images/math/0062c26909b3e07ee8f5a6285b2563d69bc979ff.png b/docs/_build/html/_images/math/0062c26909b3e07ee8f5a6285b2563d69bc979ff.png new file mode 100644 index 00000000..5972dea3 Binary files /dev/null and b/docs/_build/html/_images/math/0062c26909b3e07ee8f5a6285b2563d69bc979ff.png differ diff --git a/docs/_build/html/_images/math/0b7c1e16a3a8a849bb8ffdcdbf86f65fd1f30438.png b/docs/_build/html/_images/math/0b7c1e16a3a8a849bb8ffdcdbf86f65fd1f30438.png new file mode 100644 index 00000000..a18e0a63 Binary files /dev/null and b/docs/_build/html/_images/math/0b7c1e16a3a8a849bb8ffdcdbf86f65fd1f30438.png differ diff --git a/docs/_build/html/_images/math/12535500db0213985b2cce27db85468b60985da0.png b/docs/_build/html/_images/math/12535500db0213985b2cce27db85468b60985da0.png new file mode 100644 index 00000000..3f14f83e Binary files /dev/null and b/docs/_build/html/_images/math/12535500db0213985b2cce27db85468b60985da0.png differ diff --git a/docs/_build/html/_images/math/14f247b2a9ccd480e67621eb0d8a2d7a68a285bd.png b/docs/_build/html/_images/math/14f247b2a9ccd480e67621eb0d8a2d7a68a285bd.png new file mode 100644 index 00000000..57cc10da Binary files /dev/null and b/docs/_build/html/_images/math/14f247b2a9ccd480e67621eb0d8a2d7a68a285bd.png differ diff --git a/docs/_build/html/_images/math/1a2da53015c9d2f2c52257723f812bf512b6818e.png b/docs/_build/html/_images/math/1a2da53015c9d2f2c52257723f812bf512b6818e.png new file mode 100644 index 00000000..a5d0f0f5 Binary files /dev/null and b/docs/_build/html/_images/math/1a2da53015c9d2f2c52257723f812bf512b6818e.png differ diff --git a/docs/_build/html/_images/math/1b5e577d6216dca3af7d87aa122a0b9b360d6cb3.png b/docs/_build/html/_images/math/1b5e577d6216dca3af7d87aa122a0b9b360d6cb3.png new file mode 100644 index 00000000..17d4718d Binary files /dev/null and b/docs/_build/html/_images/math/1b5e577d6216dca3af7d87aa122a0b9b360d6cb3.png differ diff --git a/docs/_build/html/_images/math/20582dab63cb7f6604f5bf70224030ad3411ae16.png b/docs/_build/html/_images/math/20582dab63cb7f6604f5bf70224030ad3411ae16.png new file mode 100644 index 00000000..3c953660 Binary files /dev/null and b/docs/_build/html/_images/math/20582dab63cb7f6604f5bf70224030ad3411ae16.png differ diff --git a/docs/_build/html/_images/math/21fc11b715eef698662fe1cc017d7ae2d53320d8.png b/docs/_build/html/_images/math/21fc11b715eef698662fe1cc017d7ae2d53320d8.png new file mode 100644 index 00000000..e93d6cd8 Binary files /dev/null and b/docs/_build/html/_images/math/21fc11b715eef698662fe1cc017d7ae2d53320d8.png differ diff --git a/docs/_build/html/_images/math/26b09a13f97c2e89eb7687980b95a54839775fc8.png b/docs/_build/html/_images/math/26b09a13f97c2e89eb7687980b95a54839775fc8.png new file mode 100644 index 00000000..30b3c443 Binary files /dev/null and b/docs/_build/html/_images/math/26b09a13f97c2e89eb7687980b95a54839775fc8.png differ diff --git a/docs/_build/html/_images/math/276f7e256cbddeb81eee42e1efc348f3cb4ab5f8.png b/docs/_build/html/_images/math/276f7e256cbddeb81eee42e1efc348f3cb4ab5f8.png new file mode 100644 index 00000000..17d4718d Binary files /dev/null and b/docs/_build/html/_images/math/276f7e256cbddeb81eee42e1efc348f3cb4ab5f8.png differ diff --git a/docs/_build/html/_images/math/2bf86ca220f43e569c6c7aefaf32742919222e6e.png b/docs/_build/html/_images/math/2bf86ca220f43e569c6c7aefaf32742919222e6e.png new file mode 100644 index 00000000..3f14f83e Binary files /dev/null and b/docs/_build/html/_images/math/2bf86ca220f43e569c6c7aefaf32742919222e6e.png differ diff --git a/docs/_build/html/_images/math/2f5aa019312e1bbc969deab8dca8b00f76025404.png b/docs/_build/html/_images/math/2f5aa019312e1bbc969deab8dca8b00f76025404.png new file mode 100644 index 00000000..6e6726a6 Binary files /dev/null and b/docs/_build/html/_images/math/2f5aa019312e1bbc969deab8dca8b00f76025404.png differ diff --git a/docs/_build/html/_images/math/2fc62c02be8e341b7c32727e07b633086e70ba8a.png b/docs/_build/html/_images/math/2fc62c02be8e341b7c32727e07b633086e70ba8a.png new file mode 100644 index 00000000..c8c9a4c1 Binary files /dev/null and b/docs/_build/html/_images/math/2fc62c02be8e341b7c32727e07b633086e70ba8a.png differ diff --git a/docs/_build/html/_images/math/31668e57cb798f64d05820e7ec7ed4172b84a5ac.png b/docs/_build/html/_images/math/31668e57cb798f64d05820e7ec7ed4172b84a5ac.png new file mode 100644 index 00000000..c8c9a4c1 Binary files /dev/null and b/docs/_build/html/_images/math/31668e57cb798f64d05820e7ec7ed4172b84a5ac.png differ diff --git a/docs/_build/html/_images/math/334b0728f25dd84a28e483181038a307ea6e483e.png b/docs/_build/html/_images/math/334b0728f25dd84a28e483181038a307ea6e483e.png new file mode 100644 index 00000000..3c953660 Binary files /dev/null and b/docs/_build/html/_images/math/334b0728f25dd84a28e483181038a307ea6e483e.png differ diff --git a/docs/_build/html/_images/math/3d927294dd9fee97ab16ad5032baf0c08cdda4c2.png b/docs/_build/html/_images/math/3d927294dd9fee97ab16ad5032baf0c08cdda4c2.png new file mode 100644 index 00000000..fb274eb9 Binary files /dev/null and b/docs/_build/html/_images/math/3d927294dd9fee97ab16ad5032baf0c08cdda4c2.png differ diff --git a/docs/_build/html/_images/math/42858b59a7270363c15ca14b0d5fc56d33af1f8a.png b/docs/_build/html/_images/math/42858b59a7270363c15ca14b0d5fc56d33af1f8a.png new file mode 100644 index 00000000..57cc10da Binary files /dev/null and b/docs/_build/html/_images/math/42858b59a7270363c15ca14b0d5fc56d33af1f8a.png differ diff --git a/docs/_build/html/_images/math/44ad79f4db8277d12b1a6f5def2d30122c89b9b0.png b/docs/_build/html/_images/math/44ad79f4db8277d12b1a6f5def2d30122c89b9b0.png new file mode 100644 index 00000000..fc518bb0 Binary files /dev/null and b/docs/_build/html/_images/math/44ad79f4db8277d12b1a6f5def2d30122c89b9b0.png differ diff --git a/docs/_build/html/_images/math/499a2ee48b33448e80b97af9df9550828bdbfb59.png b/docs/_build/html/_images/math/499a2ee48b33448e80b97af9df9550828bdbfb59.png new file mode 100644 index 00000000..024cb035 Binary files /dev/null and b/docs/_build/html/_images/math/499a2ee48b33448e80b97af9df9550828bdbfb59.png differ diff --git a/docs/_build/html/_images/math/503d970dfd19d2f6bfd5f3d3876a74d8816cbf70.png b/docs/_build/html/_images/math/503d970dfd19d2f6bfd5f3d3876a74d8816cbf70.png new file mode 100644 index 00000000..20ed40ec Binary files /dev/null and b/docs/_build/html/_images/math/503d970dfd19d2f6bfd5f3d3876a74d8816cbf70.png differ diff --git a/docs/_build/html/_images/math/5406eadc281dbd20de843b0034c8497320dae5cb.png b/docs/_build/html/_images/math/5406eadc281dbd20de843b0034c8497320dae5cb.png new file mode 100644 index 00000000..0501afa5 Binary files /dev/null and b/docs/_build/html/_images/math/5406eadc281dbd20de843b0034c8497320dae5cb.png differ diff --git a/docs/_build/html/_images/math/557d5fad862c9046d26e1a930f45a550c146d592.png b/docs/_build/html/_images/math/557d5fad862c9046d26e1a930f45a550c146d592.png new file mode 100644 index 00000000..f2860cbc Binary files /dev/null and b/docs/_build/html/_images/math/557d5fad862c9046d26e1a930f45a550c146d592.png differ diff --git a/docs/_build/html/_images/math/5aa339d4daf45a810dda332e3c80a0698e526e04.png b/docs/_build/html/_images/math/5aa339d4daf45a810dda332e3c80a0698e526e04.png new file mode 100644 index 00000000..59594eb6 Binary files /dev/null and b/docs/_build/html/_images/math/5aa339d4daf45a810dda332e3c80a0698e526e04.png differ diff --git a/docs/_build/html/_images/math/5c8ff70420eb65f959d5e2a9b244fc565d9ecbd6.png b/docs/_build/html/_images/math/5c8ff70420eb65f959d5e2a9b244fc565d9ecbd6.png new file mode 100644 index 00000000..394e410c Binary files /dev/null and b/docs/_build/html/_images/math/5c8ff70420eb65f959d5e2a9b244fc565d9ecbd6.png differ diff --git a/docs/_build/html/_images/math/5f8c562c89b6bf12d27dc6cdc9dc090f7bb78e9c.png b/docs/_build/html/_images/math/5f8c562c89b6bf12d27dc6cdc9dc090f7bb78e9c.png new file mode 100644 index 00000000..d4114f38 Binary files /dev/null and b/docs/_build/html/_images/math/5f8c562c89b6bf12d27dc6cdc9dc090f7bb78e9c.png differ diff --git a/docs/_build/html/_images/math/64932505aadaa1eac6316ff09c2c3b101b068168.png b/docs/_build/html/_images/math/64932505aadaa1eac6316ff09c2c3b101b068168.png new file mode 100644 index 00000000..394e410c Binary files /dev/null and b/docs/_build/html/_images/math/64932505aadaa1eac6316ff09c2c3b101b068168.png differ diff --git a/docs/_build/html/_images/math/667eda4bf3d5ce33b6cc785cadfef79bb95741ca.png b/docs/_build/html/_images/math/667eda4bf3d5ce33b6cc785cadfef79bb95741ca.png new file mode 100644 index 00000000..d457103b Binary files /dev/null and b/docs/_build/html/_images/math/667eda4bf3d5ce33b6cc785cadfef79bb95741ca.png differ diff --git a/docs/_build/html/_images/math/6b21e0b0899a0d2879d3b8019087fa630bab4ea2.png b/docs/_build/html/_images/math/6b21e0b0899a0d2879d3b8019087fa630bab4ea2.png new file mode 100644 index 00000000..04a54342 Binary files /dev/null and b/docs/_build/html/_images/math/6b21e0b0899a0d2879d3b8019087fa630bab4ea2.png differ diff --git a/docs/_build/html/_images/math/6de62736d8aa90101801d7a1416e97e921d1620f.png b/docs/_build/html/_images/math/6de62736d8aa90101801d7a1416e97e921d1620f.png new file mode 100644 index 00000000..0c909ba2 Binary files /dev/null and b/docs/_build/html/_images/math/6de62736d8aa90101801d7a1416e97e921d1620f.png differ diff --git a/docs/_build/html/_images/math/70852cea72eb5c4d0a45c0db08b49476f4404426.png b/docs/_build/html/_images/math/70852cea72eb5c4d0a45c0db08b49476f4404426.png new file mode 100644 index 00000000..efb8757f Binary files /dev/null and b/docs/_build/html/_images/math/70852cea72eb5c4d0a45c0db08b49476f4404426.png differ diff --git a/docs/_build/html/_images/math/736e3543dff2b2b07e3f3373aa7663c49a6afc5f.png b/docs/_build/html/_images/math/736e3543dff2b2b07e3f3373aa7663c49a6afc5f.png new file mode 100644 index 00000000..a78d831d Binary files /dev/null and b/docs/_build/html/_images/math/736e3543dff2b2b07e3f3373aa7663c49a6afc5f.png differ diff --git a/docs/_build/html/_images/math/7720e563212e11bf72de255ab82c2a3b97c1a7f5.png b/docs/_build/html/_images/math/7720e563212e11bf72de255ab82c2a3b97c1a7f5.png new file mode 100644 index 00000000..0281a4b3 Binary files /dev/null and b/docs/_build/html/_images/math/7720e563212e11bf72de255ab82c2a3b97c1a7f5.png differ diff --git a/docs/_build/html/_images/math/789735c1db036ea36cd0aa25a3af4b2528ed3abe.png b/docs/_build/html/_images/math/789735c1db036ea36cd0aa25a3af4b2528ed3abe.png new file mode 100644 index 00000000..92a24742 Binary files /dev/null and b/docs/_build/html/_images/math/789735c1db036ea36cd0aa25a3af4b2528ed3abe.png differ diff --git a/docs/_build/html/_images/math/78b6e77cc610bf56f5c64cb6dcc6d6ee49f886f9.png b/docs/_build/html/_images/math/78b6e77cc610bf56f5c64cb6dcc6d6ee49f886f9.png new file mode 100644 index 00000000..fb274eb9 Binary files /dev/null and b/docs/_build/html/_images/math/78b6e77cc610bf56f5c64cb6dcc6d6ee49f886f9.png differ diff --git a/docs/_build/html/_images/math/7a3d0c9264473a58cc3a769f99a662631131377c.png b/docs/_build/html/_images/math/7a3d0c9264473a58cc3a769f99a662631131377c.png new file mode 100644 index 00000000..0501afa5 Binary files /dev/null and b/docs/_build/html/_images/math/7a3d0c9264473a58cc3a769f99a662631131377c.png differ diff --git a/docs/_build/html/_images/math/7a7bb470119808e2db2879fc2b2526f467b7a40b.png b/docs/_build/html/_images/math/7a7bb470119808e2db2879fc2b2526f467b7a40b.png new file mode 100644 index 00000000..03832e37 Binary files /dev/null and b/docs/_build/html/_images/math/7a7bb470119808e2db2879fc2b2526f467b7a40b.png differ diff --git a/docs/_build/html/_images/math/7b5b384bfd47bd6e8d707b3189aaab3a46c5ed04.png b/docs/_build/html/_images/math/7b5b384bfd47bd6e8d707b3189aaab3a46c5ed04.png new file mode 100644 index 00000000..626242f1 Binary files /dev/null and b/docs/_build/html/_images/math/7b5b384bfd47bd6e8d707b3189aaab3a46c5ed04.png differ diff --git a/docs/_build/html/_images/math/7bce831f5eba0d0e0fcb0cced170b2926804500a.png b/docs/_build/html/_images/math/7bce831f5eba0d0e0fcb0cced170b2926804500a.png new file mode 100644 index 00000000..d4114f38 Binary files /dev/null and b/docs/_build/html/_images/math/7bce831f5eba0d0e0fcb0cced170b2926804500a.png differ diff --git a/docs/_build/html/_images/math/7daf0d4815e763eb90f0d5f1dc406f668c1e21db.png b/docs/_build/html/_images/math/7daf0d4815e763eb90f0d5f1dc406f668c1e21db.png new file mode 100644 index 00000000..5972dea3 Binary files /dev/null and b/docs/_build/html/_images/math/7daf0d4815e763eb90f0d5f1dc406f668c1e21db.png differ diff --git a/docs/_build/html/_images/math/877d234f4cec6974ce218fc2e975a486a7972dfd.png b/docs/_build/html/_images/math/877d234f4cec6974ce218fc2e975a486a7972dfd.png new file mode 100644 index 00000000..6e6726a6 Binary files /dev/null and b/docs/_build/html/_images/math/877d234f4cec6974ce218fc2e975a486a7972dfd.png differ diff --git a/docs/_build/html/_images/math/90efcfdee16eaaec575238e2e6df5f731c8609bf.png b/docs/_build/html/_images/math/90efcfdee16eaaec575238e2e6df5f731c8609bf.png new file mode 100644 index 00000000..626242f1 Binary files /dev/null and b/docs/_build/html/_images/math/90efcfdee16eaaec575238e2e6df5f731c8609bf.png differ diff --git a/docs/_build/html/_images/math/914b2d4b6659b86d3153d5510839dfb254dfc8a3.png b/docs/_build/html/_images/math/914b2d4b6659b86d3153d5510839dfb254dfc8a3.png new file mode 100644 index 00000000..f2860cbc Binary files /dev/null and b/docs/_build/html/_images/math/914b2d4b6659b86d3153d5510839dfb254dfc8a3.png differ diff --git a/docs/_build/html/_images/math/95f028ab2b20b895fa12d986e0d9f40f7b6e52d3.png b/docs/_build/html/_images/math/95f028ab2b20b895fa12d986e0d9f40f7b6e52d3.png new file mode 100644 index 00000000..6d61d4f8 Binary files /dev/null and b/docs/_build/html/_images/math/95f028ab2b20b895fa12d986e0d9f40f7b6e52d3.png differ diff --git a/docs/_build/html/_images/math/9630132210b904754c9ab272b61cb527d12263ca.png b/docs/_build/html/_images/math/9630132210b904754c9ab272b61cb527d12263ca.png new file mode 100644 index 00000000..a18e0a63 Binary files /dev/null and b/docs/_build/html/_images/math/9630132210b904754c9ab272b61cb527d12263ca.png differ diff --git a/docs/_build/html/_images/math/9c353382eebb42a8a9dec3a426d346d4842bd39d.png b/docs/_build/html/_images/math/9c353382eebb42a8a9dec3a426d346d4842bd39d.png new file mode 100644 index 00000000..633ed9ac Binary files /dev/null and b/docs/_build/html/_images/math/9c353382eebb42a8a9dec3a426d346d4842bd39d.png differ diff --git a/docs/_build/html/_images/math/9c7f8f771e36601a75e3520845155d09080f6281.png b/docs/_build/html/_images/math/9c7f8f771e36601a75e3520845155d09080f6281.png new file mode 100644 index 00000000..92a24742 Binary files /dev/null and b/docs/_build/html/_images/math/9c7f8f771e36601a75e3520845155d09080f6281.png differ diff --git a/docs/_build/html/_images/math/a23635874ee5c9865debbf9c8686e9367f2850e5.png b/docs/_build/html/_images/math/a23635874ee5c9865debbf9c8686e9367f2850e5.png new file mode 100644 index 00000000..30b3c443 Binary files /dev/null and b/docs/_build/html/_images/math/a23635874ee5c9865debbf9c8686e9367f2850e5.png differ diff --git a/docs/_build/html/_images/math/ab1718d730cd7b17532f760861e9659b68a65156.png b/docs/_build/html/_images/math/ab1718d730cd7b17532f760861e9659b68a65156.png new file mode 100644 index 00000000..a5d0f0f5 Binary files /dev/null and b/docs/_build/html/_images/math/ab1718d730cd7b17532f760861e9659b68a65156.png differ diff --git a/docs/_build/html/_images/math/ab9afdaf786ce53318d75d81f050af8560822fcd.png b/docs/_build/html/_images/math/ab9afdaf786ce53318d75d81f050af8560822fcd.png new file mode 100644 index 00000000..aceb072a Binary files /dev/null and b/docs/_build/html/_images/math/ab9afdaf786ce53318d75d81f050af8560822fcd.png differ diff --git a/docs/_build/html/_images/math/b62df33f46595ed60c51f255186ee346c1fcc0cb.png b/docs/_build/html/_images/math/b62df33f46595ed60c51f255186ee346c1fcc0cb.png new file mode 100644 index 00000000..6041d28b Binary files /dev/null and b/docs/_build/html/_images/math/b62df33f46595ed60c51f255186ee346c1fcc0cb.png differ diff --git a/docs/_build/html/_images/math/b6c245d487949782a89cab9ee83504a62fdc2337.png b/docs/_build/html/_images/math/b6c245d487949782a89cab9ee83504a62fdc2337.png new file mode 100644 index 00000000..024cb035 Binary files /dev/null and b/docs/_build/html/_images/math/b6c245d487949782a89cab9ee83504a62fdc2337.png differ diff --git a/docs/_build/html/_images/math/bcb2457ac9d8995a4f34d57cadac7ecbbe58f3bd.png b/docs/_build/html/_images/math/bcb2457ac9d8995a4f34d57cadac7ecbbe58f3bd.png new file mode 100644 index 00000000..6d61d4f8 Binary files /dev/null and b/docs/_build/html/_images/math/bcb2457ac9d8995a4f34d57cadac7ecbbe58f3bd.png differ diff --git a/docs/_build/html/_images/math/bdb2d04d69b82c2288f5ef46664d548355e130af.png b/docs/_build/html/_images/math/bdb2d04d69b82c2288f5ef46664d548355e130af.png new file mode 100644 index 00000000..aceb072a Binary files /dev/null and b/docs/_build/html/_images/math/bdb2d04d69b82c2288f5ef46664d548355e130af.png differ diff --git a/docs/_build/html/_images/math/be07aa32325c7a6161c0cef04f9b702054873211.png b/docs/_build/html/_images/math/be07aa32325c7a6161c0cef04f9b702054873211.png new file mode 100644 index 00000000..e93d6cd8 Binary files /dev/null and b/docs/_build/html/_images/math/be07aa32325c7a6161c0cef04f9b702054873211.png differ diff --git a/docs/_build/html/_images/math/c0d0f97cd9bb4e6571e2689163f9f2989b304f55.png b/docs/_build/html/_images/math/c0d0f97cd9bb4e6571e2689163f9f2989b304f55.png new file mode 100644 index 00000000..0c909ba2 Binary files /dev/null and b/docs/_build/html/_images/math/c0d0f97cd9bb4e6571e2689163f9f2989b304f55.png differ diff --git a/docs/_build/html/_images/math/c4ed4f54ee00448249d2df22ad67f3e281df085b.png b/docs/_build/html/_images/math/c4ed4f54ee00448249d2df22ad67f3e281df085b.png new file mode 100644 index 00000000..01805a45 Binary files /dev/null and b/docs/_build/html/_images/math/c4ed4f54ee00448249d2df22ad67f3e281df085b.png differ diff --git a/docs/_build/html/_images/math/c67734af70861b2bd4dedf5c41c9aad231466f84.png b/docs/_build/html/_images/math/c67734af70861b2bd4dedf5c41c9aad231466f84.png new file mode 100644 index 00000000..0281a4b3 Binary files /dev/null and b/docs/_build/html/_images/math/c67734af70861b2bd4dedf5c41c9aad231466f84.png differ diff --git a/docs/_build/html/_images/math/d226d17c031c4b264fb8f59f953381951cc2e9b4.png b/docs/_build/html/_images/math/d226d17c031c4b264fb8f59f953381951cc2e9b4.png new file mode 100644 index 00000000..20ed40ec Binary files /dev/null and b/docs/_build/html/_images/math/d226d17c031c4b264fb8f59f953381951cc2e9b4.png differ diff --git a/docs/_build/html/_images/math/d6d56a5dd20011b190ba97d6f36d154e11c9035c.png b/docs/_build/html/_images/math/d6d56a5dd20011b190ba97d6f36d154e11c9035c.png new file mode 100644 index 00000000..6041d28b Binary files /dev/null and b/docs/_build/html/_images/math/d6d56a5dd20011b190ba97d6f36d154e11c9035c.png differ diff --git a/docs/_build/html/_images/math/d843100cd0a02e1e5184694228a7ac41e19e8af2.png b/docs/_build/html/_images/math/d843100cd0a02e1e5184694228a7ac41e19e8af2.png new file mode 100644 index 00000000..d457103b Binary files /dev/null and b/docs/_build/html/_images/math/d843100cd0a02e1e5184694228a7ac41e19e8af2.png differ diff --git a/docs/_build/html/_images/math/d8ee85ac4d75924cdf4b18f5fb3b46550932fc26.png b/docs/_build/html/_images/math/d8ee85ac4d75924cdf4b18f5fb3b46550932fc26.png new file mode 100644 index 00000000..050fa073 Binary files /dev/null and b/docs/_build/html/_images/math/d8ee85ac4d75924cdf4b18f5fb3b46550932fc26.png differ diff --git a/docs/_build/html/_images/math/db3d34854a6f48587cf5b9a41df90ad1c5e332d6.png b/docs/_build/html/_images/math/db3d34854a6f48587cf5b9a41df90ad1c5e332d6.png new file mode 100644 index 00000000..efb8757f Binary files /dev/null and b/docs/_build/html/_images/math/db3d34854a6f48587cf5b9a41df90ad1c5e332d6.png differ diff --git a/docs/_build/html/_images/math/df0deb143e5ac127f00bd248ee8001ecae572adc.png b/docs/_build/html/_images/math/df0deb143e5ac127f00bd248ee8001ecae572adc.png new file mode 100644 index 00000000..59594eb6 Binary files /dev/null and b/docs/_build/html/_images/math/df0deb143e5ac127f00bd248ee8001ecae572adc.png differ diff --git a/docs/_build/html/_images/math/e03ea88dda29ab6daa19c839ff37b8115e0a10e1.png b/docs/_build/html/_images/math/e03ea88dda29ab6daa19c839ff37b8115e0a10e1.png new file mode 100644 index 00000000..633ed9ac Binary files /dev/null and b/docs/_build/html/_images/math/e03ea88dda29ab6daa19c839ff37b8115e0a10e1.png differ diff --git a/docs/_build/html/_images/math/e3fc28292267f066fee7718c64f4bbfece521f24.png b/docs/_build/html/_images/math/e3fc28292267f066fee7718c64f4bbfece521f24.png new file mode 100644 index 00000000..04a54342 Binary files /dev/null and b/docs/_build/html/_images/math/e3fc28292267f066fee7718c64f4bbfece521f24.png differ diff --git a/docs/_build/html/_images/math/e486de19dbb61c9a63584f6e4ddd6e92ef03d665.png b/docs/_build/html/_images/math/e486de19dbb61c9a63584f6e4ddd6e92ef03d665.png new file mode 100644 index 00000000..050fa073 Binary files /dev/null and b/docs/_build/html/_images/math/e486de19dbb61c9a63584f6e4ddd6e92ef03d665.png differ diff --git a/docs/_build/html/_images/math/e655092ec45224f09927d0ed9e6fcdfbddb67754.png b/docs/_build/html/_images/math/e655092ec45224f09927d0ed9e6fcdfbddb67754.png new file mode 100644 index 00000000..a78d831d Binary files /dev/null and b/docs/_build/html/_images/math/e655092ec45224f09927d0ed9e6fcdfbddb67754.png differ diff --git a/docs/_build/html/_images/math/ed38fa24f1c94891bd312012aab3f6673be3eb83.png b/docs/_build/html/_images/math/ed38fa24f1c94891bd312012aab3f6673be3eb83.png new file mode 100644 index 00000000..03832e37 Binary files /dev/null and b/docs/_build/html/_images/math/ed38fa24f1c94891bd312012aab3f6673be3eb83.png differ diff --git a/docs/_build/html/_images/math/f75f28dbcbec776fbe031f8ac923f83f9260bd8a.png b/docs/_build/html/_images/math/f75f28dbcbec776fbe031f8ac923f83f9260bd8a.png new file mode 100644 index 00000000..01805a45 Binary files /dev/null and b/docs/_build/html/_images/math/f75f28dbcbec776fbe031f8ac923f83f9260bd8a.png differ diff --git a/docs/_build/html/_images/math/fff7b4153a6590df59f8ed526be56220045b7f3b.png b/docs/_build/html/_images/math/fff7b4153a6590df59f8ed526be56220045b7f3b.png new file mode 100644 index 00000000..fc518bb0 Binary files /dev/null and b/docs/_build/html/_images/math/fff7b4153a6590df59f8ed526be56220045b7f3b.png differ diff --git a/docs/_build/html/_images/mci_schematic.png b/docs/_build/html/_images/mci_schematic.png new file mode 100644 index 00000000..8d0d4a01 Binary files /dev/null and b/docs/_build/html/_images/mci_schematic.png differ diff --git a/docs/_build/html/_modules/abc.html b/docs/_build/html/_modules/abc.html new file mode 100644 index 00000000..904d5dd2 --- /dev/null +++ b/docs/_build/html/_modules/abc.html @@ -0,0 +1,247 @@ + + + + + + + abc — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

Source code for abc

+# Copyright 2007 Google, Inc. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Abstract Base Classes (ABCs) according to PEP 3119."""
+
+
+def abstractmethod(funcobj):
+    """A decorator indicating abstract methods.
+
+    Requires that the metaclass is ABCMeta or derived from it.  A
+    class that has a metaclass derived from ABCMeta cannot be
+    instantiated unless all of its abstract methods are overridden.
+    The abstract methods can be called using any of the normal
+    'super' call mechanisms.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractmethod
+            def my_abstract_method(self, ...):
+                ...
+    """
+    funcobj.__isabstractmethod__ = True
+    return funcobj
+
+
+class abstractclassmethod(classmethod):
+    """A decorator indicating abstract classmethods.
+
+    Similar to abstractmethod.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractclassmethod
+            def my_abstract_classmethod(cls, ...):
+                ...
+
+    'abstractclassmethod' is deprecated. Use 'classmethod' with
+    'abstractmethod' instead.
+    """
+
+    __isabstractmethod__ = True
+
+    def __init__(self, callable):
+        callable.__isabstractmethod__ = True
+        super().__init__(callable)
+
+
+class abstractstaticmethod(staticmethod):
+    """A decorator indicating abstract staticmethods.
+
+    Similar to abstractmethod.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractstaticmethod
+            def my_abstract_staticmethod(...):
+                ...
+
+    'abstractstaticmethod' is deprecated. Use 'staticmethod' with
+    'abstractmethod' instead.
+    """
+
+    __isabstractmethod__ = True
+
+    def __init__(self, callable):
+        callable.__isabstractmethod__ = True
+        super().__init__(callable)
+
+
+class abstractproperty(property):
+    """A decorator indicating abstract properties.
+
+    Requires that the metaclass is ABCMeta or derived from it.  A
+    class that has a metaclass derived from ABCMeta cannot be
+    instantiated unless all of its abstract properties are overridden.
+    The abstract properties can be called using any of the normal
+    'super' call mechanisms.
+
+    Usage:
+
+        class C(metaclass=ABCMeta):
+            @abstractproperty
+            def my_abstract_property(self):
+                ...
+
+    This defines a read-only property; you can also define a read-write
+    abstract property using the 'long' form of property declaration:
+
+        class C(metaclass=ABCMeta):
+            def getx(self): ...
+            def setx(self, value): ...
+            x = abstractproperty(getx, setx)
+
+    'abstractproperty' is deprecated. Use 'property' with 'abstractmethod'
+    instead.
+    """
+
+    __isabstractmethod__ = True
+
+
+try:
+    from _abc import (get_cache_token, _abc_init, _abc_register,
+                      _abc_instancecheck, _abc_subclasscheck, _get_dump,
+                      _reset_registry, _reset_caches)
+except ImportError:
+    from _py_abc import ABCMeta, get_cache_token
+    ABCMeta.__module__ = 'abc'
+else:
+    class ABCMeta(type):
+        """Metaclass for defining Abstract Base Classes (ABCs).
+
+        Use this metaclass to create an ABC.  An ABC can be subclassed
+        directly, and then acts as a mix-in class.  You can also register
+        unrelated concrete classes (even built-in classes) and unrelated
+        ABCs as 'virtual subclasses' -- these and their descendants will
+        be considered subclasses of the registering ABC by the built-in
+        issubclass() function, but the registering ABC won't show up in
+        their MRO (Method Resolution Order) nor will method
+        implementations defined by the registering ABC be callable (not
+        even via super()).
+        """
+        def __new__(mcls, name, bases, namespace, **kwargs):
+            cls = super().__new__(mcls, name, bases, namespace, **kwargs)
+            _abc_init(cls)
+            return cls
+
+        def register(cls, subclass):
+            """Register a virtual subclass of an ABC.
+
+            Returns the subclass, to allow usage as a class decorator.
+            """
+            return _abc_register(cls, subclass)
+
+        def __instancecheck__(cls, instance):
+            """Override for isinstance(instance, cls)."""
+            return _abc_instancecheck(cls, instance)
+
+        def __subclasscheck__(cls, subclass):
+            """Override for issubclass(subclass, cls)."""
+            return _abc_subclasscheck(cls, subclass)
+
+        def _dump_registry(cls, file=None):
+            """Debug helper to print the ABC registry."""
+            print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
+            print(f"Inv. counter: {get_cache_token()}", file=file)
+            (_abc_registry, _abc_cache, _abc_negative_cache,
+             _abc_negative_cache_version) = _get_dump(cls)
+            print(f"_abc_registry: {_abc_registry!r}", file=file)
+            print(f"_abc_cache: {_abc_cache!r}", file=file)
+            print(f"_abc_negative_cache: {_abc_negative_cache!r}", file=file)
+            print(f"_abc_negative_cache_version: {_abc_negative_cache_version!r}",
+                  file=file)
+
+        def _abc_registry_clear(cls):
+            """Clear the registry (for debugging or testing)."""
+            _reset_registry(cls)
+
+        def _abc_caches_clear(cls):
+            """Clear the caches (for debugging or testing)."""
+            _reset_caches(cls)
+
+
+class ABC(metaclass=ABCMeta):
+    """Helper class that provides a standard way to create an ABC using
+    inheritance.
+    """
+    __slots__ = ()
+
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html new file mode 100644 index 00000000..168591e8 --- /dev/null +++ b/docs/_build/html/_modules/index.html @@ -0,0 +1,81 @@ + + + + + + + Overview: module code — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+ +
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/tigramite/data_processing.html b/docs/_build/html/_modules/tigramite/data_processing.html new file mode 100644 index 00000000..02bfc193 --- /dev/null +++ b/docs/_build/html/_modules/tigramite/data_processing.html @@ -0,0 +1,1261 @@ + + + + + + + tigramite.data_processing — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

Source code for tigramite.data_processing

+"""Tigramite data processing functions."""
+
+# Author: Jakob Runge <jakob@jakob-runge.com>
+#
+# License: GNU General Public License v3.0
+from __future__ import print_function
+from collections import defaultdict, OrderedDict
+import sys
+import warnings
+import copy
+import numpy as np
+import scipy.sparse
+import scipy.sparse.linalg
+
+
[docs]class DataFrame(): + """Data object containing time series array and optional mask. + + Alternatively, a panda dataframe can be used. + + Parameters + ---------- + data : array-like + Numpy array of shape (observations T, variables N) + + mask : array-like, optional (default: None) + Optional mask array, must be of same shape as data + + Attributes + ---------- + data : array-like + Numpy array of shape (observations T, variables N) + + mask : array-like, optional (default: None) + Optional mask array, must be of same shape as data + + missing_flag : number, optional (default: None) + Flag for missing values in dataframe. Dismisses all time slices of + samples where missing values occur in any variable and also flags + samples for all lags up to 2*tau_max. This avoids biases, see + section on masking in Supplement of [1]_. + + var_names : list of strings, optional (default: range(N)) + Names of variables, must match the number of variables. If None is + passed, variables are enumerated as [0, 1, ...] + + datatime : array-like, optional (default: None) + Timelabel array. If None, range(T) is used. + """ + def __init__(self, data, mask=None, missing_flag=None, var_names=None, + datatime=None): + + self.values = data + self.mask = mask + self.missing_flag = missing_flag + T, N = data.shape + # Set the variable names + self.var_names = var_names + # Set the default variable names if none are set + if self.var_names is None: + self.var_names = {i: i for i in range(N)} + + # Set datatime + self.datatime = datatime + if self.datatime is None: + self.datatime = np.arange(T) + + # if type(self.values) != np.ndarray: + # raise TypeError("data is of type %s, " % type(self.values) + + # "must be np.ndarray") + if N > T: + warnings.warn("data.shape = %s," % str(self.values.shape) + + " is it of shape (observations, variables) ?") + # if np.isnan(data).sum() != 0: + # raise ValueError("NaNs in the data") + self._check_mask() + + def _check_mask(self, mask=None, require_mask=False): + """Checks that the mask is: + * The same shape as the data + * Is an numpy ndarray (or subtype) + * Does not contain any NaN entrie + + Parameters + ---------- + require_mask : bool (default : False) + """ + # Check that there is a mask if required + _use_mask = mask + if _use_mask is None: + _use_mask = self.mask + if require_mask and _use_mask is None: + raise ValueError("Expected a mask, but got nothing!") + # If we have a mask, check it + if _use_mask is not None: + # Check the mask inherets from an ndarray + if not isinstance(_use_mask, np.ndarray): + raise TypeError("mask is of type %s, " % + type(_use_mask) + + "must be numpy.ndarray") + # Check if there is an nan-value in the mask + if np.isnan(np.sum(_use_mask)): + raise ValueError("NaNs in the data mask") + # Check the mask and the values have the same shape + if self.values.shape != _use_mask.shape: + raise ValueError("shape mismatch: dataframe.values.shape = %s" + % str(self.values.shape) + \ + " but mask.shape = %s," + % str(_use_mask.shape)) + \ + "must identical" + +
[docs] def construct_array(self, X, Y, Z, tau_max, + mask=None, + mask_type=None, + return_cleaned_xyz=False, + do_checks=True, + cut_off='2xtau_max', + verbosity=0): + """Constructs array from variables X, Y, Z from data. + + Data is of shape (T, N), where T is the time series length and N the + number of variables. + + Parameters + ---------- + X, Y, Z : list of tuples + For a dependence measure I(X;Y|Z), Y is of the form [(varY, 0)], + where var specifies the variable index. X typically is of the form + [(varX, -tau)] with tau denoting the time lag and Z can be + multivariate [(var1, -lag), (var2, -lag), ...] . + + tau_max : int + Maximum time lag. This may be used to make sure that estimates for + different lags in X and Z all have the same sample size. + + mask : array-like, optional (default: None) + Optional mask array, must be of same shape as data. If it is set, + then it overrides the self.mask assigned to the dataframe. If it is + None, then the self.mask is used, if it exists. + + mask_type : {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence + measure I(X; Y | Z) the samples should be masked. If None, 'y' is + used, which excludes all time slices containing masked samples in Y. + Explained in [1]_. + + return_cleaned_xyz : bool, optional (default: False) + Whether to return cleaned X,Y,Z, where possible duplicates are + removed. + + do_checks : bool, optional (default: True) + Whether to perform sanity checks on input X,Y,Z + + cut_off : {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'} + How many samples to cutoff at the beginning. The default is + '2xtau_max', which guarantees that MCI tests are all conducted on + the same samples. For modeling, 'max_lag_or_tau_max' can be used, + which uses the maximum of tau_max and the conditions, which is + useful to compare multiple models on the same sample. Last, + 'max_lag' uses as much samples as possible. + + verbosity : int, optional (default: 0) + Level of verbosity. + + Returns + ------- + array, xyz [,XYZ] : Tuple of data array of shape (dim, T) and xyz + identifier array of shape (dim,) identifying which row in array + corresponds to X, Y, and Z. For example:: + X = [(0, -1)], Y = [(1, 0)], Z = [(1, -1), (0, -2)] + yields an array of shape (5, T) and xyz is + xyz = numpy.array([0,1,2,2]) + If return_cleaned_xyz is True, also outputs the cleaned XYZ lists. + """ + # Get the length in time and the number of nodes + T, N = self.values.shape + + # Remove duplicates in X, Y, Z + X = list(OrderedDict.fromkeys(X)) + Y = list(OrderedDict.fromkeys(Y)) + Z = list(OrderedDict.fromkeys(Z)) + + # If a node in Z occurs already in X or Y, remove it from Z + Z = [node for node in Z if (node not in X) and (node not in Y)] + + # Check that all lags are non-positive and indices are in [0,N-1] + XYZ = X + Y + Z + dim = len(XYZ) + + # Ensure that XYZ makes sense + if do_checks: + self._check_nodes(Y, XYZ, N, dim) + + # Figure out what cut off we will be using + if cut_off == '2xtau_max': + max_lag = 2*tau_max + elif cut_off == 'max_lag': + max_lag = abs(np.array(XYZ)[:, 1].min()) + elif cut_off == 'max_lag_or_tau_max': + max_lag = max(abs(np.array(XYZ)[:, 1].min()), tau_max) + else: + raise ValueError("max_lag must be in {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'}") + + + # Setup XYZ identifier + index_code = {'x' : 0, + 'y' : 1, + 'z' : 2} + xyz = np.array([index_code[name] + for var, name in zip([X, Y, Z], ['x', 'y', 'z']) + for _ in var]) + + # Setup and fill array with lagged time series + time_length = T - max_lag + array = np.zeros((dim, time_length), dtype=self.values.dtype) + # Note, lags are negative here + for i, (var, lag) in enumerate(XYZ): + array[i, :] = self.values[max_lag + lag:T + lag, var] + + # Choose which indices to use + use_indices = np.ones(time_length, dtype='int') + + # Remove all values that have missing value flag, as well as the time + # slices that occur up to max_lag after + if self.missing_flag is not None: + missing_anywhere = np.any(self.values == self.missing_flag, axis=1) + for tau in range(max_lag+1): + use_indices[missing_anywhere[tau:T-max_lag+tau]] = 0 + + # Use the mask override if needed + _use_mask = mask + if _use_mask is None: + _use_mask = self.mask + else: + self._check_mask(mask=_use_mask) + + if _use_mask is not None: + # Remove samples with mask == 1 conditional on which mask_type is + # used Create an array selector that is the same shape as the output + # array + array_mask = np.zeros((dim, time_length), dtype='int32') + # Iterate over all nodes named in X, Y, or Z + for i, (var, lag) in enumerate(XYZ): + # Transform the mask into the output array shape, i.e. from data + # mask to array mask + array_mask[i, :] = (_use_mask[max_lag + lag: T + lag, var] == False) + # Iterate over defined mapping from letter index to number index, + # i.e. 'x' -> 0, 'y' -> 1, 'z'-> 2 + for idx, cde in index_code.items(): + # Check if the letter index is in the mask type + if (mask_type is not None) and (idx in mask_type): + # If so, check if any of the data that correspond to the + # letter index is masked by taking the product along the + # node-data to return a time slice selection, where 0 means + # the time slice will not be used + slice_select = np.prod(array_mask[xyz == cde, :], axis=0) + use_indices *= slice_select + + if (self.missing_flag is not None) or (_use_mask is not None): + if use_indices.sum() == 0: + raise ValueError("No unmasked samples") + array = array[:, use_indices == 1] + + # Print information about the constructed array + if verbosity > 2: + self.print_array_info(array, X, Y, Z, self.missing_flag, mask_type) + + # Return the array and xyz and optionally (X, Y, Z) + if return_cleaned_xyz: + return array, xyz, (X, Y, Z) + return array, xyz
+ + def _check_nodes(self, Y, XYZ, N, dim): + """ + Checks that: + * The requests XYZ nodes have the correct shape + * All lags are non-positive + * All indices are less than N + * One of the Y nodes has zero lag + + Parameters + ---------- + Y : list of tuples + Of the form [(var, -tau)], where var specifies the variable + index and tau the time lag. + + XYZ : list of tuples + List of nodes chosen for current independence test + + N : int + Total number of listed nodes + + dim : int + Number of nodes excluding repeated nodes + """ + if np.array(XYZ).shape != (dim, 2): + raise ValueError("X, Y, Z must be lists of tuples in format" + " [(var, -lag),...], eg., [(2, -2), (1, 0), ...]") + if np.any(np.array(XYZ)[:, 1] > 0): + raise ValueError("nodes are %s, " % str(XYZ) + + "but all lags must be non-positive") + if (np.any(np.array(XYZ)[:, 0] >= N) + or np.any(np.array(XYZ)[:, 0] < 0)): + raise ValueError("var indices %s," % str(np.array(XYZ)[:, 0]) + + " but must be in [0, %d]" % (N - 1)) + if np.all(np.array(Y)[:, 1] != 0): + raise ValueError("Y-nodes are %s, " % str(Y) + + "but one of the Y-nodes must have zero lag") + +
[docs] def print_array_info(self, array, X, Y, Z, missing_flag, mask_type): + """ + Print info about the constructed array + + Parameters + ---------- + array : Data array of shape (dim, T) + + X, Y, Z : list of tuples + For a dependence measure I(X;Y|Z), Y is of the form [(varY, 0)], + where var specifies the variable index. X typically is of the form + [(varX, -tau)] with tau denoting the time lag and Z can be + multivariate [(var1, -lag), (var2, -lag), ...] . + + missing_flag : number, optional (default: None) + Flag for missing values. Dismisses all time slices of samples where + missing values occur in any variable and also flags samples for all + lags up to 2*tau_max. This avoids biases, see section on masking in + Supplement of [1]_. + + mask_type : {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence + measure I(X; Y | Z) the samples should be masked. If None, 'y' is + used, which excludes all time slices containing masked samples in Y. + Explained in [1]_. + """ + indt = " " * 12 + print(indt + "Constructed array of shape %s from"%str(array.shape) + + "\n" + indt + "X = %s" % str(X) + + "\n" + indt + "Y = %s" % str(Y) + + "\n" + indt + "Z = %s" % str(Z)) + if self.mask is not None: + print(indt+"with masked samples in %s removed" % mask_type) + if self.missing_flag is not None: + print(indt+"with missing values = %s removed" % self.missing_flag)
+ + + +
[docs]def lowhighpass_filter(data, cutperiod, pass_periods='low'): + """Butterworth low- or high pass filter. + + This function applies a linear filter twice, once forward and once + backwards. The combined filter has linear phase. + + Parameters + ---------- + data : array + Data array of shape (time, variables). + + cutperiod : int + Period of cutoff. + + pass_periods : str, optional (default: 'low') + Either 'low' or 'high' to act as a low- or high-pass filter + + Returns + ------- + data : array + Filtered data array. + """ + try: + from scipy.signal import butter, filtfilt + except: + print('Could not import scipy.signal for butterworth filtering!') + + fs = 1. + order = 3 + ws = 1. / cutperiod / (0.5 * fs) + b, a = butter(order, ws, pass_periods) + if np.ndim(data) == 1: + data = filtfilt(b, a, data) + else: + for i in range(data.shape[1]): + data[:, i] = filtfilt(b, a, data[:, i]) + + return data
+ + +
[docs]def smooth(data, smooth_width, kernel='gaussian', + mask=None, residuals=False): + """Returns either smoothed time series or its residuals. + + the difference between the original and the smoothed time series + (=residuals) of a kernel smoothing with gaussian (smoothing kernel width = + twice the sigma!) or heaviside window, equivalent to a running mean. + + Assumes data of shape (T, N) or (T,) + :rtype: array + :returns: smoothed/residual data + + Parameters + ---------- + data : array + Data array of shape (time, variables). + + smooth_width : float + Window width of smoothing, 2*sigma for a gaussian. + + kernel : str, optional (default: 'gaussian') + Smoothing kernel, 'gaussian' or 'heaviside' for a running mean. + + mask : bool array, optional (default: None) + Data mask where True labels masked samples. + + residuals : bool, optional (default: False) + True if residuals should be returned instead of smoothed data. + + Returns + ------- + data : array-like + Smoothed/residual data. + """ + + print("%s %s smoothing with " % ({True: "Take residuals of a ", + False: ""}[residuals], kernel) + + "window width %.2f (2*sigma for a gaussian!)" % (smooth_width)) + + totaltime = len(data) + if kernel == 'gaussian': + window = np.exp(-(np.arange(totaltime).reshape((1, totaltime)) - + np.arange(totaltime).reshape((totaltime, 1)) + ) ** 2 / ((2. * smooth_width / 2.) ** 2)) + elif kernel == 'heaviside': + import scipy.linalg + wtmp = np.zeros(totaltime) + wtmp[:np.ceil(smooth_width / 2.)] = 1 + window = scipy.linalg.toeplitz(wtmp) + + if mask is None: + if np.ndim(data) == 1: + smoothed_data = (data * window).sum(axis=1) / window.sum(axis=1) + else: + smoothed_data = np.zeros(data.shape) + for i in range(data.shape[1]): + smoothed_data[:, i] = ( + data[:, i] * window).sum(axis=1) / window.sum(axis=1) + else: + if np.ndim(data) == 1: + smoothed_data = ((data * window * (mask==False)).sum(axis=1) / + (window * (mask==False)).sum(axis=1)) + else: + smoothed_data = np.zeros(data.shape) + for i in range(data.shape[1]): + smoothed_data[:, i] = (( + data[:, i] * window * (mask==False)[:, i]).sum(axis=1) / + (window * (mask==False)[:, i]).sum(axis=1)) + + if residuals: + return data - smoothed_data + else: + return smoothed_data
+ + +
[docs]def weighted_avg_and_std(values, axis, weights): + """Returns the weighted average and standard deviation. + + Parameters + --------- + values : array + Data array of shape (time, variables). + + axis : int + Axis to average/std about + + weights : array + Weight array of shape (time, variables). + + Returns + ------- + (average, std) : tuple of arrays + Tuple of weighted average and standard deviation along axis. + """ + + values[np.isnan(values)] = 0. + average = np.ma.average(values, axis=axis, weights=weights) + + variance = np.sum(weights * (values - np.expand_dims(average, axis) + ) ** 2, axis=axis) / weights.sum(axis=axis) + + return (average, np.sqrt(variance))
+ + +
[docs]def time_bin_with_mask(data, time_bin_length, mask=None): + """Returns time binned data where only about non-masked values is averaged. + + Parameters + ---------- + data : array + Data array of shape (time, variables). + + time_bin_length : int + Length of time bin. + + mask : bool array, optional (default: None) + Data mask where True labels masked samples. + + Returns + ------- + (bindata, T) : tuple of array and int + Tuple of time-binned data array and new length of array. + """ + + T = len(data) + + time_bin_length = int(time_bin_length) + + if mask is None: + sample_selector = np.ones(data.shape) + else: + # Invert mask + sample_selector = (mask == False) + + if np.ndim(data) == 1.: + data.shape = (T, 1) + mask.shape = (T, 1) + + bindata = np.zeros( + (T // time_bin_length,) + data.shape[1:], dtype="float32") + for index, i in enumerate(range(0, T - time_bin_length + 1, + time_bin_length)): + # print weighted_avg_and_std(fulldata[i:i+time_bin_length], axis=0, + # weights=sample_selector[i:i+time_bin_length])[0] + bindata[index] = weighted_avg_and_std(data[i:i + time_bin_length], + axis=0, + weights=sample_selector[i:i + + time_bin_length])[0] + + T, grid_size = bindata.shape + + return (bindata.squeeze(), T)
+ + +
[docs]def ordinal_patt_array(array, array_mask=None, dim=2, step=1, + weights=False, verbosity=0): + """Returns symbolified array of ordinal patterns. + + Each data vector (X_t, ..., X_t+(dim-1)*step) is converted to its rank + vector. E.g., (0.2, -.6, 1.2) --> (1,0,2) which is then assigned to a + unique integer (see Article). There are faculty(dim) possible rank vectors. + + Note that the symb_array is step*(dim-1) shorter than the original array! + + Reference: B. Pompe and J. Runge (2011). Momentary information transfer as + a coupling measure of time series. Phys. Rev. E, 83(5), 1-12. + doi:10.1103/PhysRevE.83.051122 + + Parameters + ---------- + array : array-like + Data array of shape (time, variables). + + array_mask : bool array + Data mask where True labels masked samples. + + dim : int, optional (default: 2) + Pattern dimension + + step : int, optional (default: 1) + Delay of pattern embedding vector. + + weights : bool, optional (default: False) + Whether to return array of variances of embedding vectors as weights. + + verbosity : int, optional (default: 0) + Level of verbosity. + + Returns + ------- + patt, patt_mask [, patt_time] : tuple of arrays + Tuple of converted pattern array and new length + """ + from scipy.misc import factorial + + # Import cython code + try: + import tigramite.tigramite_cython_code as tigramite_cython_code + except ImportError: + raise ImportError("Could not import tigramite_cython_code, please" + " compile cython code first as described in Readme.") + + array = array.astype('float64') + + if array_mask is not None: + assert array_mask.dtype == 'int32' + else: + array_mask = np.zeros(array.shape, dtype='int32') + + + if np.ndim(array) == 1: + T = len(array) + array = array.reshape(T, 1) + array_mask = array_mask.reshape(T, 1) + + # Add noise to destroy ties... + array += (1E-6 * array.std(axis=0) + * np.random.rand(array.shape[0], array.shape[1]).astype('float64')) + + + patt_time = int(array.shape[0] - step * (dim - 1)) + T, N = array.shape + + if dim <= 1 or patt_time <= 0: + raise ValueError("Dim mist be > 1 and length of delay vector smaller " + "array length.") + + patt = np.zeros((patt_time, N), dtype='int32') + weights_array = np.zeros((patt_time, N), dtype='float64') + + patt_mask = np.zeros((patt_time, N), dtype='int32') + + # Precompute factorial for c-code... patterns of dimension + # larger than 10 are not supported + fac = factorial(np.arange(10)).astype('int32') + + # _get_patterns_cython assumes mask=0 to be a masked value + array_mask = (array_mask == False).astype('int32') + + (patt, patt_mask, weights_array) = \ + tigramite_cython_code._get_patterns_cython(array, array_mask, + patt, patt_mask, + weights_array, dim, + step, fac, N, T) + + weights_array = np.asarray(weights_array) + patt = np.asarray(patt) + # Transform back to mask=1 implying a masked value + patt_mask = np.asarray(patt_mask) == False + + if weights: + return (patt, patt_mask, patt_time, weights_array) + else: + return (patt, patt_mask, patt_time)
+ + +
[docs]def quantile_bin_array(data, bins=6): + """Returns symbolified array with equal-quantile binning. + + Parameters + ---------- + data : array + Data array of shape (time, variables). + + bins : int, optional (default: 6) + Number of bins. + + Returns + ------- + symb_array : array + Converted data of integer type. + """ + T, N = data.shape + + # get the bin quantile steps + bin_edge = int(np.ceil(T / float(bins))) + + symb_array = np.zeros((T, N), dtype='int32') + + # get the lower edges of the bins for every time series + edges = np.sort(data, axis=0)[::bin_edge, :].T + bins = edges.shape[1] + + # This gives the symbolic time series + symb_array = (data.reshape(T, N, 1) >= edges.reshape(1, N, bins)).sum( + axis=2) - 1 + + return symb_array.astype('int32')
+ +def _generate_noise(covar_matrix, time=1000, use_inverse=False): + """ + Generate a multivariate normal distribution using correlated innovations. + + Parameters + ---------- + covar_matrix : array + Covariance matrix of the random variables + + time : int + Sample size + + use_inverse : bool, optional + Negate the off-diagonal elements and invert the covariance matrix + before use + + Returns + ------- + noise : array + Random noise generated according to covar_matrix + """ + # Pull out the number of nodes from the shape of the covar_matrix + n_nodes = covar_matrix.shape[0] + # Make a deep copy for use in the inverse case + this_covar = covar_matrix + # Take the negative inverse if needed + if use_inverse: + this_covar = copy.deepcopy(covar_matrix) + this_covar *= -1 + this_covar[np.diag_indices_from(this_covar)] *= -1 + this_covar = np.linalg.inv(this_covar) + # Return the noise distribution + return np.random.multivariate_normal(mean=np.zeros(n_nodes), + cov=this_covar, + size=time) + +def _check_stability(graph): + """ + Raises an AssertionError if the input graph corresponds to a non-stationary + process. + + Parameters + ---------- + graph : array + Lagged connectivity matrices. Shape is (n_nodes, n_nodes, max_delay+1) + """ + # Get the shape from the input graph + n_nodes, _, period = graph.shape + # Set the top section as the horizontally stacked matrix of + # shape (n_nodes, n_nodes * period) + stability_matrix = \ + scipy.sparse.hstack([scipy.sparse.lil_matrix(graph[:, :, t_slice]) + for t_slice in range(period)]) + # Extend an identity matrix of shape + # (n_nodes * (period - 1), n_nodes * (period - 1)) to shape + # (n_nodes * (period - 1), n_nodes * period) and stack the top section on + # top to make the stability matrix of shape + # (n_nodes * period, n_nodes * period) + stability_matrix = \ + scipy.sparse.vstack([stability_matrix, + scipy.sparse.eye(n_nodes * (period - 1), + n_nodes * period)]) + # Check the number of dimensions to see if we can afford to use a dense + # matrix + n_eigs = stability_matrix.shape[0] + if n_eigs <= 25: + # If it is relatively low in dimensionality, use a dense array + stability_matrix = stability_matrix.todense() + eigen_values, _ = scipy.linalg.eig(stability_matrix) + else: + # If it is a large dimensionality, convert to a compressed row sorted + # matrix, as it may be easier for the linear algebra package + stability_matrix = stability_matrix.tocsr() + # Get the eigen values of the stability matrix + eigen_values = scipy.sparse.linalg.eigs(stability_matrix, + k=(n_eigs - 2), + return_eigenvectors=False) + # Ensure they all have less than one magnitude + assert np.all(np.abs(eigen_values) < 1.), \ + "Values given by time lagged connectivity matrix corresponds to a "+\ + " non-stationary process!" + +def _check_initial_values(initial_values, shape): + """ + Raises a AssertionError if the input initial values: + * Are not a numpy array OR + * Do not have the shape (n_nodes, max_delay+1) + + Parameters + ---------- + graph : array + Lagged connectivity matrices. Shape is (n_nodes, n_nodes, max_delay+1) + """ + # Ensure it is a numpy array + assert isinstance(initial_values, np.ndarray),\ + "User must provide initial_values as a numpy.ndarray" + # Check the shape is correct + assert initial_values.shape == shape,\ + "Initial values must be of shape (n_nodes, max_delay+1)"+\ + "\n current shape : " + str(initial_values.shape)+\ + "\n desired shape : " + str(shape) + +def _var_network(graph, + add_noise=True, + inno_cov=None, + invert_inno=False, + T=100, + initial_values=None): + """Returns a vector-autoregressive process with correlated innovations. + + Useful for testing. + + Example: + graph=numpy.array([[[0.2,0.,0.],[0.5,0.,0.]], + [[0.,0.1,0. ],[0.3,0.,0.]]]) + + represents a process + + X_1(t) = 0.2 X_1(t-1) + 0.5 X_2(t-1) + eps_1(t) + X_2(t) = 0.3 X_2(t-1) + 0.1 X_1(t-2) + eps_2(t) + + with inv_inno_cov being the negative (except for diagonal) inverse + covariance matrix of (eps_1(t), eps_2(t)) OR inno_cov being + the covariance. Initial values can also be provided. + + + Parameters + ---------- + graph : array + Lagged connectivity matrices. Shape is (n_nodes, n_nodes, max_delay+1) + + add_noise : bool, optional (default: True) + Flag to add random noise or not + + inno_cov : array, optional (default: None) + Covariance matrix of innovations. + + invert_inno : bool, optional (defualt : False) + Flag to negate off-diagonal elements of inno_cov and invert it before + using it as the covariance matrix of innovations + + T : int, optional (default: 100) + Sample size. + + initial_values : array, optional (defult: None) + Initial values for each node. Shape is (n_nodes, max_delay+1), i.e. must + be of shape (graph.shape[1], graph.shape[2]). + + Returns + ------- + X : array + Array of realization. + """ + n_nodes, _, period = graph.shape + + time = T + # Test stability + _check_stability(graph) + + # Generate the returned data + data = np.random.randn(n_nodes, time) + # Load the initial values + if initial_values is not None: + # Check the shape of the initial values + _check_initial_values(initial_values, data[:, :period].shape) + # Input the initial values + data[:, :period] = initial_values + + # Check if we are adding noise + noise = None + if add_noise: + # Use inno_cov if it was provided + if inno_cov is not None: + noise = _generate_noise(inno_cov, + time=time, + use_inverse=invert_inno) + # Otherwise just use uncorrelated random noise + else: + noise = np.random.randn(time, n_nodes) + + for a_time in range(period, time): + data_past = np.repeat( + data[:, a_time-period:a_time][:, ::-1].reshape(1, n_nodes, period), + n_nodes, axis=0) + data[:, a_time] = (data_past*graph).sum(axis=2).sum(axis=1) + if add_noise: + data[:, a_time] += noise[a_time] + + return data.transpose() + +def _iter_coeffs(parents_neighbors_coeffs): + """ + Iterator through the current parents_neighbors_coeffs structure. Mainly to + save repeated code and make it easier to change this structure. + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} for + all variables where vars must be in [0..N-1] and lags <= 0 with number + of variables N. + + Yields + ------- + (node_id, parent_id, time_lag, coeff) : tuple + Tuple defining the relationship between nodes across time + """ + # Iterate through all defined nodes + for node_id in list(parents_neighbors_coeffs): + # Iterate over parent nodes and unpack node and coeff + for (parent_id, time_lag), coeff in parents_neighbors_coeffs[node_id]: + # Yield the entry + yield node_id, parent_id, time_lag, coeff + +def _check_parent_neighbor(parents_neighbors_coeffs): + """ + Checks to insure input parent-neighbor connectivity input is sane. This + means that: + * all time lags are non-positive + * all parent nodes are included as nodes themselves + * all node indexing is contiguous + * all node indexing starts from zero + Raises a ValueError if any one of these conditions are not met. + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} for + all variables where vars must be in [0..N-1] and lags <= 0 with number + of variables N. + """ + # Initialize some lists for checking later + all_nodes = set() + all_parents = set() + # Iterate through variables + for j in list(parents_neighbors_coeffs): + # Cache all node ids to ensure they are contiguous + all_nodes.add(j) + # Iterate through all nodes + for j, i, tau, _ in _iter_coeffs(parents_neighbors_coeffs): + # Check all time lags are equal to or less than zero + if tau > 0: + raise ValueError("Lag between parent {} and node {}".format(i, j)+\ + " is {} > 0, must be <= 0!".format(tau)) + # Cache all parent ids to ensure they are mentioned as node ids + all_parents.add(i) + # Check that all nodes are contiguous from zero + all_nodes_list = sorted(list(all_nodes)) + if all_nodes_list != list(range(len(all_nodes_list))): + raise ValueError("Node IDs in input dictionary must be contiguous"+\ + " and start from zero!\n"+\ + " Found IDs : [" +\ + ",".join(map(str, all_nodes_list))+ "]") + # Check that all parent nodes are mentioned as a node ID + if not all_parents.issubset(all_nodes): + missing_nodes = sorted(list(all_parents - all_nodes)) + all_parents_list = sorted(list(all_parents)) + raise ValueError("Parent IDs in input dictionary must also be in set"+\ + " of node IDs."+\ + "\n Parent IDs "+" ".join(map(str, all_parents_list))+\ + "\n Node IDs "+" ".join(map(str, all_nodes_list)) +\ + "\n Missing IDs " + " ".join(map(str, missing_nodes))) + +def _check_symmetric_relations(a_matrix): + """ + Check if the argument matrix is symmetric. Raise a value error with details + about the offending elements if it is not. This is useful for checking the + instantaneously linked nodes have the same link strength. + + Parameters + ---------- + a_matrix : 2D numpy array + Relationships between nodes at tau = 0. Indexed such that first index is + node and second is parent, i.e. node j with parent i has strength + a_matrix[j,i] + """ + # Check it is symmetric + if not np.allclose(a_matrix, a_matrix.T, rtol=1e-10, atol=1e-10): + # Store the disagreement elements + bad_elems = ~np.isclose(a_matrix, a_matrix.T, rtol=1e-10, atol=1e-10) + bad_idxs = np.argwhere(bad_elems) + error_message = "" + for node, parent in bad_idxs: + # Check that we haven't already printed about this pair + if bad_elems[node, parent]: + error_message += \ + "Parent {:d} of node {:d}".format(parent, node)+\ + " has coefficient {:f}.\n".format(a_matrix[node, parent])+\ + "Parent {:d} of node {:d}".format(node, parent)+\ + " has coefficient {:f}.\n".format(a_matrix[parent, node]) + # Check if we already printed about this one + bad_elems[node, parent] = False + bad_elems[parent, node] = False + raise ValueError("Relationships between nodes at tau=0 are not"+\ + " symmetric!\n"+error_message) + +def _find_max_time_lag_and_node_id(parents_neighbors_coeffs): + """ + Function to find the maximum time lag in the parent-neighbors-coefficients + object, as well as the largest node ID + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} for + all variables where vars must be in [0..N-1] and lags <= 0 with number + of variables N. + + Returns + ------- + (max_time_lag, max_node_id) : tuple + Tuple of the maximum time lag and maximum node ID + """ + # Default maximum lag and node ID + max_time_lag = 0 + max_node_id = 0 + # Iterate through the keys in parents_neighbors_coeffs + for j, _, tau, _ in _iter_coeffs(parents_neighbors_coeffs): + # Find max lag time + max_time_lag = max(max_time_lag, abs(tau)) + # Find the max node ID + max_node_id = max(max_node_id, j) + # Return these values + return max_time_lag, max_node_id + +def _get_true_parent_neighbor_dict(parents_neighbors_coeffs): + """ + Function to return the dictionary of true parent neighbor causal + connections in time. + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} for + all variables where vars must be in [0..N-1] and lags <= 0 with number + of variables N. + + Returns + ------- + true_parent_neighbor : dict + Dictionary of lists of tuples. The dictionary is keyed by node ID, the + list stores the tuple values (parent_node_id, time_lag) + """ + # Initialize the returned dictionary of lists + true_parents_neighbors = defaultdict(list) + for j, i, tau, coeff in _iter_coeffs(parents_neighbors_coeffs): + # Add parent node id and lag if non-zero coeff + if coeff != 0.: + true_parents_neighbors[j].append((i, tau)) + # Return the true relations + return true_parents_neighbors + +def _get_covariance_matrix(parents_neighbors_coeffs): + """ + Determines the covariance matrix for correlated innovations + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} for + all variables where vars must be in [0..N-1] and lags <= 0 with number + of variables N. + + Returns + ------- + covar_matrix : numpy array + Covariance matrix implied by the parents_neighbors_coeffs. Used to + generate correlated innovations. + """ + # Get the total number of nodes + _, max_node_id = \ + _find_max_time_lag_and_node_id(parents_neighbors_coeffs) + n_nodes = max_node_id + 1 + # Initialize the covariance matrix + covar_matrix = np.identity(n_nodes) + # Iterate through all the node connections + for j, i, tau, coeff in _iter_coeffs(parents_neighbors_coeffs): + # Add to covar_matrix if node connection is instantaneous + if tau == 0: + covar_matrix[j, i] = coeff + return covar_matrix + +def _get_lag_connect_matrix(parents_neighbors_coeffs): + """ + Generates the lagged connectivity matrix from a parent-neighbor + connectivity dictionary. Used to generate the input for _var_network + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} for + all variables where vars must be in [0..N-1] and lags <= 0 with number + of variables N. + + Returns + ------- + connect_matrix : numpy array + Lagged connectivity matrix. Shape is (n_nodes, n_nodes, max_delay+1) + """ + # Get the total number of nodes and time lag + max_time_lag, max_node_id = \ + _find_max_time_lag_and_node_id(parents_neighbors_coeffs) + n_nodes = max_node_id + 1 + n_times = max_time_lag + 1 + # Initialize full time graph + connect_matrix = np.zeros((n_nodes, n_nodes, n_times)) + for j, i, tau, coeff in _iter_coeffs(parents_neighbors_coeffs): + # If there is a non-zero time lag, add the connection to the matrix + if tau != 0: + connect_matrix[j, i, -(tau+1)] = coeff + # Return the connectivity matrix + return connect_matrix + +
[docs]def var_process(parents_neighbors_coeffs, T=1000, use='inv_inno_cov', + verbosity=0, initial_values=None): + """Returns a vector-autoregressive process with correlated innovations. + + Wrapper around var_network with possibly more user-friendly input options. + + Parameters + ---------- + parents_neighbors_coeffs : dict + Dictionary of format: + {..., j:[((var1, lag1), coef1), ((var2, lag2), coef2), ...], ...} + for all variables where vars must be in [0..N-1] and lags <= 0 with + number of variables N. If lag=0, a nonzero value in the covariance + matrix (or its inverse) is implied. These should be the same for (i, j) + and (j, i). + + use : str, optional (default: 'inv_inno_cov') + Specifier, either 'inno_cov' or 'inv_inno_cov'. + Any other specifier will result in non-correlated noise. + For debugging, 'no_noise' can also be specified, in which case random + noise will be disabled. + + T : int, optional (default: 1000) + Sample size. + + verbosity : int, optional (default: 0) + Level of verbosity. + + initial_values : array, optional (default: None) + Initial values for each node. Shape must be (N, max_delay+1) + + Returns + ------- + data : array-like + Data generated from this process + true_parent_neighbor : dict + Dictionary of lists of tuples. The dictionary is keyed by node ID, the + list stores the tuple values (parent_node_id, time_lag) + """ + # Check the input parents_neighbors_coeffs dictionary for sanity + _check_parent_neighbor(parents_neighbors_coeffs) + # Generate the true parent neighbors graph + true_parents_neighbors = \ + _get_true_parent_neighbor_dict(parents_neighbors_coeffs) + # Generate the correlated innovations + innos = _get_covariance_matrix(parents_neighbors_coeffs) + # Generate the lagged connectivity matrix for _var_network + connect_matrix = _get_lag_connect_matrix(parents_neighbors_coeffs) + # Default values as per 'inno_cov' + add_noise = True + invert_inno = False + # Use the correlated innovations + if use == 'inno_cov': + if verbosity > 0: + print("\nInnovation Cov =\n%s" % str(innos)) + # Use the inverted correlated innovations + elif use == 'inv_inno_cov': + invert_inno = True + if verbosity > 0: + print("\nInverse Innovation Cov =\n%s" % str(innos)) + # Do not use any noise + elif use == 'no_noise': + add_noise = False + if verbosity > 0: + print("\nInverse Innovation Cov =\n%s" % str(innos)) + # Use decorrelated noise + else: + innos = None + # Ensure the innovation matrix is symmetric if it is used + if (innos is not None) and add_noise: + _check_symmetric_relations(innos) + # Generate the data using _var_network + data = _var_network(graph=connect_matrix, + add_noise=add_noise, + inno_cov=innos, + invert_inno=invert_inno, + T=T, + initial_values=initial_values) + # Return the data + return data, true_parents_neighbors
+ +class _Logger(object): + """Class to append print output to a string which can be saved""" + def __init__(self): + self.terminal = sys.stdout + self.log = "" # open("log.dat", "a") + + def write(self, message): + self.terminal.write(message) + self.log += message # .write(message) +
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/tigramite/independence_tests.html b/docs/_build/html/_modules/tigramite/independence_tests.html new file mode 100644 index 00000000..4f75a990 --- /dev/null +++ b/docs/_build/html/_modules/tigramite/independence_tests.html @@ -0,0 +1,2714 @@ + + + + + + + tigramite.independence_tests — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

Source code for tigramite.independence_tests

+"""Tigramite causal discovery for time series."""
+
+# Author: Jakob Runge <jakob@jakob-runge.com>
+#
+# License: GNU General Public License v3.0
+
+from __future__ import print_function
+import warnings
+import math
+import abc
+from scipy import special, stats, spatial
+import numpy as np
+import six
+import sys
+
+try:
+    from sklearn import gaussian_process
+except:
+    print("Could not import sklearn for Gaussian process tests")
+
+try:
+    from tigramite import tigramite_cython_code
+except:
+    print("Could not import packages for CMIknn and GPDC estimation")
+
+try:
+    import rpy2
+    import rpy2.robjects
+    rpy2.robjects.r['options'](warn=-1)
+    from rpy2.robjects.packages import importr
+    import rpy2.robjects.numpy2ri
+    rpy2.robjects.numpy2ri.activate()
+except:
+    print("Could not import rpy package")
+
+try:
+    importr('RCIT')
+except:
+    print("Could not import r-package RCIT")
+
+
[docs]@six.add_metaclass(abc.ABCMeta) +class CondIndTest(): + """Base class of conditional independence tests. + + Provides useful general functions for different independence tests such as + shuffle significance testing and bootstrap confidence estimation. Also + handles masked samples. Other test classes can inherit from this class. + + Parameters + ---------- + mask_type : str, optional (default = None) + Must be in {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence measure + I(X; Y | Z) the samples should be masked. If None, 'y' is used, which + excludes all time slices containing masked samples in Y. Explained in + [1]_. + + significance : str, optional (default: 'analytic') + Type of significance test to use. In this package 'analytic', + 'fixed_thres' and 'shuffle_test' are available. + + fixed_thres : float, optional (default: 0.1) + If significance is 'fixed_thres', this specifies the threshold for the + absolute value of the dependence measure. + + sig_samples : int, optional (default: 1000) + Number of samples for shuffle significance test. + + sig_blocklength : int, optional (default: None) + Block length for block-shuffle significance test. If None, the + block length is determined from the decay of the autocovariance as + explained in [1]_. + + confidence : str, optional (default: None) + Specify type of confidence estimation. If False, numpy.nan is returned. + 'bootstrap' can be used with any test, for ParCorr also 'analytic' is + implemented. + + conf_lev : float, optional (default: 0.9) + Two-sided confidence interval. + + conf_samples : int, optional (default: 100) + Number of samples for bootstrap. + + conf_blocklength : int, optional (default: None) + Block length for block-bootstrap. If None, the block length is + determined from the decay of the autocovariance as explained in [1]_. + + recycle_residuals : bool, optional (default: False) + Specifies whether residuals should be stored. This may be faster, but + can cost considerable memory. + + verbosity : int, optional (default: 0) + Level of verbosity. + """ +
[docs] @abc.abstractmethod + def get_dependence_measure(self, array, xyz): + """ + Abstract function that all concrete classes must instantiate. + """ + pass
+ + @abc.abstractproperty + def measure(self): + """ + Abstract property to store the type of independence test. + """ + pass + + def __init__(self, + mask_type=None, + significance='analytic', + fixed_thres=0.1, + sig_samples=1000, + sig_blocklength=None, + confidence=None, + conf_lev=0.9, + conf_samples=100, + conf_blocklength=None, + recycle_residuals=False, + verbosity=0): + # Set the dataframe to None for now, will be reset during pcmci call + self.dataframe = None + # Set the options + self.significance = significance + self.sig_samples = sig_samples + self.sig_blocklength = sig_blocklength + self.fixed_thres = fixed_thres + self.verbosity = verbosity + # If we recycle residuals, then set up a residual cache + self.recycle_residuals = recycle_residuals + if self.recycle_residuals: + self.residuals = {} + # If we use a mask, we cannot recycle residuals + self.set_mask_type(mask_type) + + # Set the confidence type and details + self.confidence = confidence + self.conf_lev = conf_lev + self.conf_samples = conf_samples + self.conf_blocklength = conf_blocklength + + # Print information about the + if self.verbosity > 0: + self.print_info() + +
[docs] def set_mask_type(self, mask_type): + """ + Setter for mask type to ensure that this option does not clash with + recycle_residuals. + + Parameters + ---------- + mask_type : str + Must be in {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence + measure I(X; Y | Z) the samples should be masked. If None, 'y' is + used, which excludes all time slices containing masked samples in Y. + Explained in [1]_. + """ + # Set the mask type + self.mask_type = mask_type + # Check if this clashes with residual recycling + if self.mask_type is not None: + if self.recycle_residuals is True: + warnings.warn("Using a mask disables recycling residuals.") + self.recycle_residuals = False + # Check the mask type is keyed correctly + self._check_mask_type()
+ +
[docs] def print_info(self): + """ + Print information about the conditional independence test parameters + """ + info_str = "\n# Initialize conditional independence test\n\nParameters:" + info_str += "\nindependence test = %s" % self.measure + info_str += "\nsignificance = %s" % self.significance + # Check if we are using a shuffle test + if self.significance == 'shuffle_test': + info_str += "\nsig_samples = %s" % self.sig_samples + info_str += "\nsig_blocklength = %s" % self.sig_blocklength + # Check if we are using a fixed threshold + elif self.significance == 'fixed_thres': + info_str += "\nfixed_thres = %s" % self.fixed_thres + # Check if we have a confidence type + if self.confidence: + info_str += "\nconfidence = %s" % self.confidence + info_str += "\nconf_lev = %s" % self.conf_lev + # Check if this confidence type is boostrapping + if self.confidence == 'bootstrap': + info_str += "\nconf_samples = %s" % self.conf_samples + info_str += "\nconf_blocklength = %s" %self.conf_blocklength + # Check if we use a non-trivial mask type + if self.mask_type is not None: + info_str += "mask_type = %s" % self.mask_type + # Check if we are recycling residuals or not + if self.recycle_residuals: + info_str += "recycle_residuals = %s" % self.recycle_residuals + # Print the information string + print(info_str)
+ + def _check_mask_type(self): + """ + mask_type : str, optional (default = None) + Must be in {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence + measure I(X; Y | Z) the samples should be masked. If None, 'y' is + used, which excludes all time slices containing masked samples in Y. + Explained in [1]_. + """ + if self.mask_type is not None: + mask_set = set(self.mask_type) - set(['x', 'y', 'z']) + if mask_set: + err_msg = "mask_type = %s," % self.mask_type + " but must be" +\ + " list containing 'x','y','z', or any combination" + raise ValueError(err_msg) + + +
[docs] def get_analytic_confidence(self, value, df, conf_lev): + """ + Base class assumption that this is not implemented. Concrete classes + should override when possible. + """ + raise NotImplementedError("Analytic confidence not"+\ + " implemented for %s" % self.measure)
+ +
[docs] def get_model_selection_criterion(self, j, parents, tau_max=0): + """ + Base class assumption that this is not implemented. Concrete classes + should override when possible. + """ + raise NotImplementedError("Model selection not"+\ + " implemented for %s" % self.measure)
+ +
[docs] def get_analytic_significance(self, value, T, dim): + """ + Base class assumption that this is not implemented. Concrete classes + should override when possible. + """ + raise NotImplementedError("Analytic significance not"+\ + " implemented for %s" % self.measure)
+ +
[docs] def get_shuffle_significance(self, array, xyz, value, + return_null_dist=False): + """ + Base class assumption that this is not implemented. Concrete classes + should override when possible. + """ + raise NotImplementedError("Shuffle significance not"+\ + " implemented for %s" % self.measure)
+ + def _get_single_residuals(self, array, target_var, + standardize=True, return_means=False): + """ + Base class assumption that this is not implemented. Concrete classes + should override when possible. + """ + raise NotImplementedError("Residual calculation not"+\ + " implemented for %s" % self.measure) + +
[docs] def set_dataframe(self, dataframe): + """Initialize and check the dataframe. + + Parameters + ---------- + dataframe : data object + Set tigramite dataframe object. It must have the attributes + dataframe.values yielding a numpy array of shape (observations T, + variables N) and optionally a mask of the same shape and a missing + values flag. + + """ + self.dataframe = dataframe + if self.mask_type is not None: + dataframe._check_mask(require_mask=True)
+ + def _keyfy(self, x, z): + """Helper function to make lists unique.""" + return (tuple(set(x)), tuple(set(z))) + + def _get_array(self, X, Y, Z, tau_max=0, cut_off='2xtau_max', verbosity=None): + """Convencience wrapper around _construct_array.""" + # Set the verbosity to the default value + if verbosity is None: + verbosity=self.verbosity + + if self.measure in ['par_corr']: + if len(X) > 1 or len(Y) > 1: + raise ValueError("X and Y for %s must be univariate." % + self.measure) + # Call the wrapped function + return self.dataframe.construct_array(X=X, Y=Y, Z=Z, + tau_max=tau_max, + mask_type=self.mask_type, + return_cleaned_xyz=True, + do_checks=False, + cut_off=cut_off, + verbosity=verbosity) + +
[docs] def run_test(self, X, Y, Z=None, tau_max=0, cut_off='2xtau_max'): + """Perform conditional independence test. + + Calls the dependence measure and signficicance test functions. The child + classes must specify a function get_dependence_measure and either or + both functions get_analytic_significance and get_shuffle_significance. + If recycle_residuals is True, also _get_single_residuals must be + available. + + Parameters + ---------- + X, Y, Z : list of tuples + X,Y,Z are of the form [(var, -tau)], where var specifies the + variable index and tau the time lag. + + tau_max : int, optional (default: 0) + Maximum time lag. This may be used to make sure that estimates for + different lags in X, Z, all have the same sample size. + + cut_off : {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'} + How many samples to cutoff at the beginning. The default is + '2xtau_max', which guarantees that MCI tests are all conducted on + the same samples. For modeling, 'max_lag_or_tau_max' can be used, + which uses the maximum of tau_max and the conditions, which is + useful to compare multiple models on the same sample. Last, + 'max_lag' uses as much samples as possible. + + Returns + ------- + val, pval : Tuple of floats + + The test statistic value and the p-value. + """ + + # Get the array to test on + array, xyz, XYZ = self._get_array(X, Y, Z, tau_max, cut_off) + X, Y, Z = XYZ + # Record the dimensions + dim, T = array.shape + # Ensure it is a valid array + if np.isnan(array).sum() != 0: + raise ValueError("nans in the array!") + # Get the dependence measure, reycling residuals if need be + val = self._get_dependence_measure_recycle(X, Y, Z, xyz, array) + # Get the p-value + pval = self.get_significance(val, array, xyz, T, dim) + # Return the value and the pvalue + return val, pval
+ +
[docs] def run_test_raw(self, x, y, z=None): + """Perform conditional independence test directly on input arrays x, y, z. + + Calls the dependence measure and signficicance test functions. The child + classes must specify a function get_dependence_measure and either or + both functions get_analytic_significance and get_shuffle_significance. + + Parameters + ---------- + x, y, z : arrays + x,y,z are of the form (samples, dimension). + + Returns + ------- + val, pval : Tuple of floats + + The test statistic value and the p-value. + """ + + if np.ndim(x) != 2 or np.ndim(y) != 2: + raise ValueError("x,y must be arrays of shape (samples, dimension)" + " where dimension can be 1.") + + if z is not None and np.ndim(z) != 2: + raise ValueError("z must be array of shape (samples, dimension)" + " where dimension can be 1.") + + if z is None: + # Get the array to test on + array = np.vstack((x.T, y.T)) + + # xyz is the dimension indicator + xyz = np.array([0 for i in range(x.shape[1])] + + [1 for i in range(y.shape[1])]) + + else: + # Get the array to test on + array = np.vstack((x.T, y.T, z.T)) + + # xyz is the dimension indicator + xyz = np.array([0 for i in range(x.shape[1])] + + [1 for i in range(y.shape[1])] + + [2 for i in range(z.shape[1])]) + + # Record the dimensions + dim, T = array.shape + # Ensure it is a valid array + if np.isnan(array).sum() != 0: + raise ValueError("nans in the array!") + # Get the dependence measure + val = self.get_dependence_measure(array, xyz) + # Get the p-value + pval = self.get_significance(val, array, xyz, T, dim) + # Return the value and the pvalue + return val, pval
+ + def _get_dependence_measure_recycle(self, X, Y, Z, xyz, array): + """Get the dependence_measure, optionally recycling residuals + + If self.recycle_residuals is True, also _get_single_residuals must be + available. + + Parameters + ---------- + X, Y, Z : list of tuples + X,Y,Z are of the form [(var, -tau)], where var specifies the + variable index and tau the time lag. + + xyz : array of ints + XYZ identifier array of shape (dim,). + + array : array + Data array of shape (dim, T) + + Return + ------ + val : float + Test statistic + """ + # Check if we are recycling residuals + if self.recycle_residuals: + # Get or calculate the cached residuals + x_resid = self._get_cached_residuals(X, Z, array, 0) + y_resid = self._get_cached_residuals(Y, Z, array, 1) + # Make a new residual array + array_resid = np.array([x_resid, y_resid]) + xyz_resid = np.array([0, 1]) + # Return the dependence measure + return self.get_dependence_measure(array_resid, xyz_resid) + # If not, return the dependence measure on the array and xyz + return self.get_dependence_measure(array, xyz) + + def _get_cached_residuals(self, x_nodes, z_nodes, array, target_var): + """ + Retrieve or calculate the cached residuals for the given node sets. + + Parameters + ---------- + x_nodes : list of tuples + List of nodes, X or Y normally. Used to key the residual cache + during lookup + + z_nodes : list of tuples + List of nodes, Z normally + + target_var : int + Key to differentiate X from Y. + x_nodes == X => 0, x_nodes == Y => 1 + + array : array + Data array of shape (dim, T) + + Returns + ------- + x_resid : array + Residuals calculated by _get_single_residual + """ + # Check if we have calculated these residuals + if self._keyfy(x_nodes, z_nodes) in list(self.residuals): + x_resid = self.residuals[self._keyfy(x_nodes, z_nodes)] + # If not, calculate the residuals + else: + x_resid = self._get_single_residuals(array, target_var=target_var) + if z_nodes: + self.residuals[self._keyfy(x_nodes, z_nodes)] = x_resid + # Return these residuals + return x_resid + +
[docs] def get_significance(self, val, array, xyz, T, dim, sig_override=None): + """ + Returns the p-value from whichever significance function is specified + for this test. If an override is used, then it will call a different + function then specified by self.significance + + Parameters + ---------- + val : float + Test statistic value. + + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + T : int + Sample length + + dim : int + Dimensionality, ie, number of features. + + sig_override : string + Must be in 'analytic', 'shuffle_test', 'fixed_thres' + + Returns + ------- + pval : float or numpy.nan + P-value. + """ + # Defaults to the self.signficance memeber value + use_sig = self.significance + if sig_override is not None: + use_sig = sig_override + # Check if we are using the analytic significance + if use_sig == 'analytic': + pval = self.get_analytic_significance(value=val, T=T, dim=dim) + # Check if we are using the shuffle significance + elif use_sig == 'shuffle_test': + pval = self.get_shuffle_significance(array=array, + xyz=xyz, + value=val) + # Check if we are using the fixed_thres significance + elif use_sig == 'fixed_thres': + pval = self.get_fixed_thres_significance( + value=val, + fixed_thres=self.fixed_thres) + else: + raise ValueError("%s not known." % self.significance) + # Return the calculated value + return pval
+ +
[docs] def get_measure(self, X, Y, Z=None, tau_max=0): + """Estimate dependence measure. + + Calls the dependence measure function. The child classes must specify + a function get_dependence_measure. + + Parameters + ---------- + X, Y [, Z] : list of tuples + X,Y,Z are of the form [(var, -tau)], where var specifies the + variable index and tau the time lag. + + tau_max : int, optional (default: 0) + Maximum time lag. This may be used to make sure that estimates for + different lags in X, Z, all have the same sample size. + + Returns + ------- + val : float + The test statistic value. + + """ + # Make the array + array, xyz, (X, Y, Z) = self._get_array(X, Y, Z, tau_max) + D, T = array.shape + # Check it is valid + if np.isnan(array).sum() != 0: + raise ValueError("nans in the array!") + # Return the dependence measure + return self._get_dependence_measure_recycle(X, Y, Z, xyz, array)
+ +
[docs] def get_confidence(self, X, Y, Z=None, tau_max=0): + """Perform confidence interval estimation. + + Calls the dependence measure and confidence test functions. The child + classes can specify a function get_dependence_measure and + get_analytic_confidence or get_bootstrap_confidence. If confidence is + False, (numpy.nan, numpy.nan) is returned. + + Parameters + ---------- + X, Y, Z : list of tuples + X,Y,Z are of the form [(var, -tau)], where var specifies the + variable index and tau the time lag. + + tau_max : int, optional (default: 0) + Maximum time lag. This may be used to make sure that estimates for + different lags in X, Z, all have the same sample size. + + Returns + ------- + (conf_lower, conf_upper) : Tuple of floats + Upper and lower confidence bound of confidence interval. + """ + # Check if a confidence type has been defined + if self.confidence: + # Ensure the confidence level given makes sense + if self.conf_lev < .5 or self.conf_lev >= 1.: + raise ValueError("conf_lev = %.2f, " % self.conf_lev + + "but must be between 0.5 and 1") + half_conf = self.conf_samples * (1. - self.conf_lev)/2. + if self.confidence == 'bootstrap' and half_conf < 1.: + raise ValueError("conf_samples*(1.-conf_lev)/2 is %.2f" + % half_conf + ", must be >> 1") + # Make and check the array + array, xyz, _ = self._get_array(X, Y, Z, tau_max, verbosity=0) + dim, T = array.shape + if np.isnan(array).sum() != 0: + raise ValueError("nans in the array!") + + # Check if we are using analytic confidence or bootstrapping it + if self.confidence == 'analytic': + val = self.get_dependence_measure(array, xyz) + (conf_lower, conf_upper) = \ + self.get_analytic_confidence(df=T-dim, + value=val, + conf_lev=self.conf_lev) + elif self.confidence == 'bootstrap': + # Overwrite analytic values + (conf_lower, conf_upper) = \ + self.get_bootstrap_confidence( + array, xyz, + conf_samples=self.conf_samples, + conf_blocklength=self.conf_blocklength, + conf_lev=self.conf_lev, verbosity=self.verbosity) + elif not self.confidence: + return None + else: + raise ValueError("%s confidence estimation not implemented" + % self.confidence) + # Cache the confidence interval + self.conf = (conf_lower, conf_upper) + # Return the confidence interval + return (conf_lower, conf_upper)
+ + def _print_cond_ind_results(self, val, pval=None, conf=None): + """Print results from conditional independence test. + + Parameters + ---------- + val : float + Test stastistic value. + + pval : float, optional (default: None) + p-value + + conf : tuple of floats, optional (default: None) + Confidence bounds. + """ + + if pval is not None: + printstr = " pval = %.5f | val = %.3f" % ( + pval, val) + if conf is not None: + printstr += " | conf bounds = (%.3f, %.3f)" % ( + conf[0], conf[1]) + else: + printstr = " val = %.3f" % val + if conf is not None: + printstr += " | conf bounds = (%.3f, %.3f)" % ( + conf[0], conf[1]) + print(printstr) + +
[docs] def get_bootstrap_confidence(self, array, xyz, dependence_measure=None, + conf_samples=100, conf_blocklength=None, + conf_lev=.95, verbosity=0): + """Perform bootstrap confidence interval estimation. + + With conf_blocklength > 1 or None a block-bootstrap is performed. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + dependence_measure : function (default = self.get_dependence_measure) + Dependence measure function must be of form + dependence_measure(array, xyz) and return a numeric value + + conf_lev : float, optional (default: 0.9) + Two-sided confidence interval. + + conf_samples : int, optional (default: 100) + Number of samples for bootstrap. + + conf_blocklength : int, optional (default: None) + Block length for block-bootstrap. If None, the block length is + determined from the decay of the autocovariance as explained in + [1]_. + + verbosity : int, optional (default: 0) + Levelof verbosity. + + Returns + ------- + (conf_lower, conf_upper) : Tuple of floats + Upper and lower confidence bound of confidence interval. + """ + # Check if a dependence measure if provided or if to use default + if not dependence_measure: + dependence_measure = self.get_dependence_measure + + # confidence interval is two-sided + c_int = 1. - (1. - conf_lev)/2. + dim, T = array.shape + + # If not block length is given, determine the optimal block length. + # This has a maximum of 10% of the time sample length + if conf_blocklength is None: + conf_blocklength = \ + self._get_block_length(array, xyz, mode='confidence') + # Determine the number of blocks total, rounding up for non-integer + # amounts + n_blks = int(math.ceil(float(T)/conf_blocklength)) + + # Print some information + if verbosity > 2: + print(" block_bootstrap confidence intervals" + " with block-length = %d ..." % conf_blocklength) + + # Generate the block bootstrapped distribution + bootdist = np.zeros(conf_samples) + for smpl in range(conf_samples): + # Get the starting indecies for the blocks + blk_strt = np.random.randint(0, T - conf_blocklength + 1, n_blks) + # Get the empty array of block resampled values + array_bootstrap = \ + np.zeros((dim, n_blks*conf_blocklength), dtype=array.dtype) + # Fill the array of block resamples + for i in range(conf_blocklength): + array_bootstrap[:, i::conf_blocklength] = array[:, blk_strt + i] + # Cut to proper length + array_bootstrap = array_bootstrap[:, :T] + + bootdist[smpl] = dependence_measure(array_bootstrap, xyz) + + # Sort and get quantile + bootdist.sort() + conf_lower = bootdist[int((1. - c_int) * conf_samples)] + conf_upper = bootdist[int(c_int * conf_samples)] + # Return the confidance limits as a tuple + return (conf_lower, conf_upper)
+ + def _get_acf(self, series, max_lag=None): + """Returns autocorrelation function. + + Parameters + ---------- + series : 1D-array + data series to compute autocorrelation from + + max_lag : int, optional (default: None) + maximum lag for autocorrelation function. If None is passed, 10% of + the data series length are used. + + Returns + ------- + autocorr : array of shape (max_lag + 1,) + Autocorrelation function. + """ + # Set the default max lag + if max_lag is None: + max_lag = int(max(5, 0.1*len(series))) + # Initialize the result + autocorr = np.ones(max_lag + 1) + # Iterate over possible lags + for lag in range(1, max_lag + 1): + # Set the values + y1_vals = series[lag:] + y2_vals = series[:len(series) - lag] + # Calculate the autocorrelation + autocorr[lag] = np.corrcoef(y1_vals, y2_vals, ddof=0)[0, 1] + return autocorr + + def _get_block_length(self, array, xyz, mode): + """Returns optimal block length for significance and confidence tests. + + Determine block length using approach in Mader (2013) [Eq. (6)] which + improves the method of Pfeifer (2005) with non-overlapping blocks In + case of multidimensional X, the max is used. Further details in [1]_. + Two modes are available. For mode='significance', only the indices + corresponding to X are shuffled in array. For mode='confidence' all + variables are jointly shuffled. If the autocorrelation curve fit fails, + a block length of 5% of T is used. The block length is limited to a + maximum of 10% of T. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + mode : str + Which mode to use. + + Returns + ------- + block_len : int + Optimal block length. + """ + # Inject a dependency on siganal, optimize + from scipy import signal, optimize + # Get the shape of the array + dim, T = array.shape + # Initiailize the indices + indices = range(dim) + if mode == 'significance': + indices = np.where(xyz == 0)[0] + + # Maximum lag for autocov estimation + max_lag = int(0.1*T) + # Define the function to optimize against + def func(x_vals, a_const, decay): + return a_const * decay**x_vals + + # Calculate the block length + block_len = 1 + for i in indices: + # Get decay rate of envelope of autocorrelation functions + # via hilbert trafo + autocov = self._get_acf(series=array[i], max_lag=max_lag) + autocov[0] = 1. + hilbert = np.abs(signal.hilbert(autocov)) + # Try to fit the curve + try: + popt, _ = optimize.curve_fit( + f=func, + xdata=np.arange(0, max_lag+1), + ydata=hilbert, + ) + phi = popt[1] + # Formula of Pfeifer (2005) assuming non-overlapping blocks + l_opt = (4. * T * (phi / (1. - phi) + phi**2 / (1. - phi)**2)**2 + / (1. + 2. * phi / (1. - phi))**2)**(1. / 3.) + block_len = max(block_len, int(l_opt)) + except RuntimeError: + print("Error - curve_fit failed in block_shuffle, using" + " block_len = %d" % (int(.05 * T))) + block_len = max(int(.05 * T), 2) + # Limit block length to a maximum of 10% of T + block_len = min(block_len, int(0.1 * T)) + return block_len + + def _get_shuffle_dist(self, array, xyz, dependence_measure, + sig_samples, sig_blocklength=None, + verbosity=0): + """Returns shuffle distribution of test statistic. + + The rows in array corresponding to the X-variable are shuffled using + a block-shuffle approach. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + dependence_measure : object + Dependence measure function must be of form + dependence_measure(array, xyz) and return a numeric value + + sig_samples : int, optional (default: 100) + Number of samples for shuffle significance test. + + sig_blocklength : int, optional (default: None) + Block length for block-shuffle significance test. If None, the + block length is determined from the decay of the autocovariance as + explained in [1]_. + + verbosity : int, optional (default: 0) + Level of verbosity. + + Returns + ------- + null_dist : array of shape (sig_samples,) + Contains the sorted test statistic values estimated from the + shuffled arrays. + """ + + dim, T = array.shape + + x_indices = np.where(xyz == 0)[0] + dim_x = len(x_indices) + + if sig_blocklength is None: + sig_blocklength = self._get_block_length(array, xyz, + mode='significance') + + n_blks = int(math.floor(float(T)/sig_blocklength)) + # print 'n_blks ', n_blks + if verbosity > 2: + print(" Significance test with block-length = %d " + "..." % (sig_blocklength)) + + array_shuffled = np.copy(array) + block_starts = np.arange(0, T - sig_blocklength + 1, sig_blocklength) + + # Dividing the array up into n_blks of length sig_blocklength may + # leave a tail. This tail is later randomly inserted + tail = array[x_indices, n_blks*sig_blocklength:] + + null_dist = np.zeros(sig_samples) + for sam in range(sig_samples): + + blk_starts = np.random.permutation(block_starts)[:n_blks] + + x_shuffled = np.zeros((dim_x, n_blks*sig_blocklength), + dtype=array.dtype) + + for i, index in enumerate(x_indices): + for blk in range(sig_blocklength): + x_shuffled[i, blk::sig_blocklength] = \ + array[index, blk_starts + blk] + + # Insert tail randomly somewhere + if tail.shape[1] > 0: + insert_tail_at = np.random.choice(block_starts) + x_shuffled = np.insert(x_shuffled, insert_tail_at, + tail.T, axis=1) + + for i, index in enumerate(x_indices): + array_shuffled[index] = x_shuffled[i] + + null_dist[sam] = dependence_measure(array=array_shuffled, + xyz=xyz) + + null_dist.sort() + + return null_dist + +
[docs] def get_fixed_thres_significance(self, value, fixed_thres): + """Returns signficance for thresholding test. + + Returns 0 if numpy.abs(value) is smaller than fixed_thres and 1 else. + + Parameters + ---------- + value : number + Value of test statistic for unshuffled estimate. + + fixed_thres : number + Fixed threshold, is made positive. + + Returns + ------- + pval : bool + Returns 0 if numpy.abs(value) is smaller than fixed_thres and 1 + else. + + """ + if np.abs(value) < np.abs(fixed_thres): + pval = 1. + else: + pval = 0. + + return pval
+ + def _trafo2uniform(self, x): + """Transforms input array to uniform marginals. + + Assumes x.shape = (dim, T) + + Parameters + ---------- + x : array-like + Input array. + + Returns + ------- + u : array-like + array with uniform marginals. + """ + + def trafo(xi): + xisorted = np.sort(xi) + yi = np.linspace(1. / len(xi), 1, len(xi)) + return np.interp(xi, xisorted, yi) + + if np.ndim(x) == 1: + u = trafo(x) + else: + u = np.empty(x.shape) + for i in range(x.shape[0]): + u[i] = trafo(x[i]) + return u
+ + +
[docs]class ParCorr(CondIndTest): + r"""Partial correlation test. + + Partial correlation is estimated through linear ordinary least squares (OLS) + regression and a test for non-zero linear Pearson correlation on the + residuals. + + Notes + ----- + To test :math:`X \perp Y | Z`, first :math:`Z` is regressed out from + :math:`X` and :math:`Y` assuming the model + + .. math:: X & = Z \beta_X + \epsilon_{X} \\ + Y & = Z \beta_Y + \epsilon_{Y} + + using OLS regression. Then the dependency of the residuals is tested with + the Pearson correlation test. + + .. math:: \rho\left(r_X, r_Y\right) + + For the ``significance='analytic'`` Student's-*t* distribution with + :math:`T-D_Z-2` degrees of freedom is implemented. + + Parameters + ---------- + **kwargs : + Arguments passed on to Parent class CondIndTest. + """ + # documentation + @property + def measure(self): + """ + Concrete property to return the measure of the independence test + """ + return self._measure + + def __init__(self, **kwargs): + self._measure = 'par_corr' + self.two_sided = True + self.residual_based = True + + CondIndTest.__init__(self, **kwargs) + + def _get_single_residuals(self, array, target_var, + standardize=True, + return_means=False): + """Returns residuals of linear multiple regression. + + Performs a OLS regression of the variable indexed by target_var on the + conditions Z. Here array is assumed to contain X and Y as the first two + rows with the remaining rows (if present) containing the conditions Z. + Optionally returns the estimated regression line. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + target_var : {0, 1} + Variable to regress out conditions from. + + standardize : bool, optional (default: True) + Whether to standardize the array beforehand. Must be used for + partial correlation. + + return_means : bool, optional (default: False) + Whether to return the estimated regression line. + + Returns + ------- + resid [, mean] : array-like + The residual of the regression and optionally the estimated line. + """ + + dim, T = array.shape + dim_z = dim - 2 + + # Standardize + if standardize: + array -= array.mean(axis=1).reshape(dim, 1) + array /= array.std(axis=1).reshape(dim, 1) + if np.isnan(array).sum() != 0: + raise ValueError("nans after standardizing, " + "possibly constant array!") + + y = array[target_var, :] + + if dim_z > 0: + z = np.fastCopyAndTranspose(array[2:, :]) + beta_hat = np.linalg.lstsq(z, y, rcond=None)[0] + mean = np.dot(z, beta_hat) + resid = y - mean + else: + resid = y + mean = None + + if return_means: + return (resid, mean) + return resid + +
[docs] def get_dependence_measure(self, array, xyz): + """Return partial correlation. + + Estimated as the Pearson correlation of the residuals of a linear + OLS regression. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + Returns + ------- + val : float + Partial correlation coefficient. + """ + + x_vals = self._get_single_residuals(array, target_var=0) + y_vals = self._get_single_residuals(array, target_var=1) + val, _ = stats.pearsonr(x_vals, y_vals) + return val
+ +
[docs] def get_shuffle_significance(self, array, xyz, value, + return_null_dist=False): + """Returns p-value for shuffle significance test. + + For residual-based test statistics only the residuals are shuffled. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + value : number + Value of test statistic for unshuffled estimate. + + Returns + ------- + pval : float + p-value + """ + + x_vals = self._get_single_residuals(array, target_var=0) + y_vals = self._get_single_residuals(array, target_var=1) + array_resid = np.array([x_vals, y_vals]) + xyz_resid = np.array([0, 1]) + + null_dist = self._get_shuffle_dist(array_resid, xyz_resid, + self.get_dependence_measure, + sig_samples=self.sig_samples, + sig_blocklength=self.sig_blocklength, + verbosity=self.verbosity) + + pval = (null_dist >= np.abs(value)).mean() + + # Adjust p-value for two-sided measures + if pval < 1.: + pval *= 2. + + if return_null_dist: + return pval, null_dist + return pval
+ +
[docs] def get_analytic_significance(self, value, T, dim): + """Returns analytic p-value from Student's t-test for the Pearson + correlation coefficient. + + Assumes two-sided correlation. If the degrees of freedom are less than + 1, numpy.nan is returned. + + Parameters + ---------- + value : float + Test statistic value. + + T : int + Sample length + + dim : int + Dimensionality, ie, number of features. + + Returns + ------- + pval : float or numpy.nan + P-value. + """ + # Get the number of degrees of freedom + deg_f = T - dim + + if deg_f < 1: + pval = np.nan + elif abs(abs(value) - 1.0) <= sys.float_info.min: + pval = 0.0 + else: + trafo_val = value * np.sqrt(deg_f/(1. - value*value)) + # Two sided significance level + pval = stats.t.sf(np.abs(trafo_val), deg_f) * 2 + + return pval
+ +
[docs] def get_analytic_confidence(self, value, df, conf_lev): + """Returns analytic confidence interval for correlation coefficient. + + Based on Student's t-distribution. + + Parameters + ---------- + value : float + Test statistic value. + + df : int + degrees of freedom of the test + + conf_lev : float + Confidence interval, eg, 0.9 + + Returns + ------- + (conf_lower, conf_upper) : Tuple of floats + Upper and lower confidence bound of confidence interval. + """ + # Confidence interval is two-sided + c_int = (1. - (1. - conf_lev) / 2.) + + value_tdist = value * np.sqrt(df) / np.sqrt(1. - value**2) + conf_lower = (stats.t.ppf(q=1. - c_int, df=df, loc=value_tdist) + / np.sqrt(df + stats.t.ppf(q=1. - c_int, df=df, + loc=value_tdist)**2)) + conf_upper = (stats.t.ppf(q=c_int, df=df, loc=value_tdist) + / np.sqrt(df + stats.t.ppf(q=c_int, df=df, + loc=value_tdist)**2)) + return (conf_lower, conf_upper)
+ + +
[docs] def get_model_selection_criterion(self, j, parents, tau_max=0): + """Returns Akaike's Information criterion modulo constants. + + Fits a linear model of the parents to variable j and returns the score. + I used to determine optimal hyperparameters in PCMCI, in particular + the pc_alpha value. + + Parameters + ---------- + j : int + Index of target variable in data array. + + parents : list + List of form [(0, -1), (3, -2), ...] containing parents. + + tau_max : int, optional (default: 0) + Maximum time lag. This may be used to make sure that estimates for + different lags in X, Z, all have the same sample size. + + Returns: + score : float + Model score. + """ + + Y = [(j, 0)] + X = [(j, 0)] # dummy variable here + Z = parents + array, xyz = self.dataframe.construct_array(X=X, Y=Y, Z=Z, + tau_max=tau_max, + mask_type=self.mask_type, + return_cleaned_xyz=False, + do_checks=False, + verbosity=self.verbosity) + + dim, T = array.shape + + y = self._get_single_residuals(array, target_var=1, return_means=False) + # Get RSS + rss = (y**2).sum() + # Number of parameters + p = dim - 1 + # Get AIC + score = T * np.log(rss) + 2. * p + return score
+ +class GaussProcReg(): + r"""Gaussian processes abstract base class. + + GP is estimated with scikit-learn and allows to flexibly specify kernels and + hyperparameters or let them be optimized automatically. The kernel specifies + the covariance function of the GP. Parameters can be passed on to + ``GaussianProcessRegressor`` using the gp_params dictionary. If None is + passed, the kernel '1.0 * RBF(1.0) + WhiteKernel()' is used with alpha=0 as + default. Note that the kernel's hyperparameters are optimized during + fitting. + + When the null distribution is not analytically available, but can be + precomputed with the function generate_and_save_nulldists(...) which saves + a \*.npz file containing the null distribution for different sample sizes. + This file can then be supplied as null_dist_filename. + + Parameters + ---------- + null_samples : int + Number of null samples to use + + cond_ind_test : CondIndTest + Conditional independence test that this Gaussian Proccess Regressor will + calculate the null distribution for. This is used to grab the + get_dependence_measure function. + + gp_version : {'new', 'old'}, optional (default: 'new') + The older GP version from scikit-learn 0.17 was used for the numerical + simulations in [1]_. The newer version from scikit-learn 0.19 is faster + and allows more flexibility regarding kernels etc. + + gp_params : dictionary, optional (default: None) + Dictionary with parameters for ``GaussianProcessRegressor``. + + null_dist_filename : str, otional (default: None) + Path to file containing null distribution. + + verbosity : int, optional (default: 0) + Level of verbosity. + """ + def __init__(self, + null_samples, + cond_ind_test, + gp_version='new', + gp_params=None, + null_dist_filename=None, + verbosity=0): + # Set the dependence measure function + self.cond_ind_test = cond_ind_test + # Set member variables + self.gp_version = gp_version + self.gp_params = gp_params + self.verbosity = verbosity + # Set the null distribution defaults + self.null_samples = null_samples + self.null_dists = {} + self.null_dist_filename = null_dist_filename + # Check if we are loading a null distrubtion from a cached file + if self.null_dist_filename is not None: + self.null_dists, self.null_samples = \ + self._load_nulldist(self.null_dist_filename) + + def _load_nulldist(self, filename): + r""" + Load a precomputed null distribution from a \*.npz file. This + distribution can be calculated using generate_and_save_nulldists(...). + + Parameters + ---------- + filename : strng + Path to the \*.npz file + + Returns + ------- + null_dists, null_samples : dict, int + The null distirbution as a dictionary of distributions keyed by + sample size, the number of null samples in total. + """ + null_dist_file = np.load(filename) + null_dists = dict(zip(null_dist_file['T'], + null_dist_file['exact_dist'])) + null_samples = len(null_dist_file['exact_dist'][0]) + return null_dists, null_samples + + def _generate_nulldist(self, df, + add_to_null_dists=True): + """Generates null distribution for pairwise independence tests. + + Generates the null distribution for sample size df. Assumes pairwise + samples transformed to uniform marginals. Uses get_dependence_measure + available in class and generates self.sig_samples random samples. Adds + the null distributions to self.null_dists. + + Parameters + ---------- + df : int + Degrees of freedom / sample size to generate null distribution for. + + add_to_null_dists : bool, optional (default: True) + Whether to add the null dist to the dictionary of null dists or + just return it. + + Returns + ------- + null_dist : array of shape [df,] + Only returned,if add_to_null_dists is False. + """ + + if self.verbosity > 0: + print("Generating null distribution for df = %d. " % df) + if add_to_null_dists: + print("For faster computations, run function " + "generate_and_save_nulldists(...) to " + "precompute null distribution and load *.npz file with " + "argument null_dist_filename") + + xyz = np.array([0,1]) + + null_dist = np.zeros(self.null_samples) + for i in range(self.null_samples): + array = np.random.rand(2, df) + null_dist[i] = self.cond_ind_test.get_dependence_measure(array, xyz) + + null_dist.sort() + if add_to_null_dists: + self.null_dists[df] = null_dist + return null_dist + + def _generate_and_save_nulldists(self, sample_sizes, null_dist_filename): + """Generates and saves null distribution for pairwise independence + tests. + + Generates the null distribution for different sample sizes. Calls + generate_nulldist. Null dists are saved to disk as + self.null_dist_filename.npz. Also adds the null distributions to + self.null_dists. + + Parameters + ---------- + sample_sizes : list + List of sample sizes. + + null_dist_filename : str + Name to save file containing null distributions. + """ + + self.null_dist_filename = null_dist_filename + + null_dists = np.zeros((len(sample_sizes), self.null_samples)) + + for iT, T in enumerate(sample_sizes): + null_dists[iT] = self._generate_nulldist(T, add_to_null_dists=False) + self.null_dists[T] = null_dists[iT] + + np.savez("%s" % null_dist_filename, + exact_dist=null_dists, + T=np.array(sample_sizes)) + + def _get_single_residuals(self, array, target_var, + return_means=False, + standardize=True, + return_likelihood=False): + """Returns residuals of Gaussian process regression. + + Performs a GP regression of the variable indexed by target_var on the + conditions Z. Here array is assumed to contain X and Y as the first two + rows with the remaining rows (if present) containing the conditions Z. + Optionally returns the estimated mean and the likelihood. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + target_var : {0, 1} + Variable to regress out conditions from. + + standardize : bool, optional (default: True) + Whether to standardize the array beforehand. + + return_means : bool, optional (default: False) + Whether to return the estimated regression line. + + return_likelihood : bool, optional (default: False) + Whether to return the log_marginal_likelihood of the fitted GP + + Returns + ------- + resid [, mean, likelihood] : array-like + The residual of the regression and optionally the estimated mean + and/or the likelihood. + """ + dim, T = array.shape + + if self.gp_params is None: + self.gp_params = {} + + if dim <= 2: + if return_likelihood: + return array[target_var, :], -np.inf + return array[target_var, :] + + # Standardize + if standardize: + array -= array.mean(axis=1).reshape(dim, 1) + array /= array.std(axis=1).reshape(dim, 1) + if np.isnan(array).sum() != 0: + raise ValueError("nans after standardizing, " + "possibly constant array!") + + target_series = array[target_var, :] + z = np.fastCopyAndTranspose(array[2:]) + if np.ndim(z) == 1: + z = z.reshape(-1, 1) + + if self.gp_version == 'old': + # Old GP failed for ties in the data + def remove_ties(series, verbosity=0): + # Test whether ties exist and add noise to destroy ties... + cnt = 0 + while len(np.unique(series)) < np.size(series): + series += 1E-6 * np.random.rand(*series.shape) + cnt += 1 + if cnt > 100: + break + return series + + z = remove_ties(z) + target_series = remove_ties(target_series) + + gp = gaussian_process.GaussianProcess( + nugget=1E-1, + thetaL=1E-16, + thetaU=np.inf, + corr='squared_exponential', + optimizer='fmin_cobyla', + regr='constant', + normalize=False, + storage_mode='light') + + elif self.gp_version == 'new': + # Overwrite default kernel and alpha values + params = self.gp_params.copy() + if 'kernel' not in list(self.gp_params): + kernel = gaussian_process.kernels.RBF() +\ + gaussian_process.kernels.WhiteKernel() + else: + kernel = self.gp_params['kernel'] + del params['kernel'] + + if 'alpha' not in list(self.gp_params): + alpha = 0. + else: + alpha = self.gp_params['alpha'] + del params['alpha'] + + gp = gaussian_process.GaussianProcessRegressor(kernel=kernel, + alpha=alpha, + **params) + + gp.fit(z, target_series.reshape(-1, 1)) + + if self.verbosity > 3 and self.gp_version == 'new': + print(kernel, alpha, gp.kernel_, gp.alpha) + + if self.verbosity > 3 and self.gp_version == 'old': + print(gp.get_params) + + if return_likelihood: + likelihood = gp.log_marginal_likelihood() + + mean = gp.predict(z).squeeze() + + resid = target_series - mean + + if return_means and not return_likelihood: + return (resid, mean) + elif return_likelihood and not return_means: + return (resid, likelihood) + elif return_means and return_likelihood: + return resid, mean, likelihood + return resid + + def _get_model_selection_criterion(self, j, parents, tau_max=0): + """Returns log marginal likelihood for GP regression. + + Fits a GP model of the parents to variable j and returns the negative + log marginal likelihood as a model selection score. Is used to determine + optimal hyperparameters in PCMCI, in particular the pc_alpha value. + + Parameters + ---------- + j : int + Index of target variable in data array. + + parents : list + List of form [(0, -1), (3, -2), ...] containing parents. + + tau_max : int, optional (default: 0) + Maximum time lag. This may be used to make sure that estimates for + different lags in X, Z, all have the same sample size. + + Returns: + score : float + Model score. + """ + + Y = [(j, 0)] + X = [(j, 0)] # dummy variable here + Z = parents + array, xyz = \ + self.cond_ind_test.dataframe.construct_array( + X=X, Y=Y, Z=Z, + tau_max=tau_max, + mask_type=self.cond_ind_test.mask_type, + return_cleaned_xyz=False, + do_checks=False, + verbosity=self.verbosity) + + dim, T = array.shape + + _, logli = self._get_single_residuals(array, + target_var=1, + return_likelihood=True) + + score = -logli + return score + +
[docs]class GPDC(CondIndTest): + r"""GPDC conditional independence test based on Gaussian processes and + distance correlation. + + GPDC is based on a Gaussian process (GP) regression and a distance + correlation test on the residuals [2]_. GP is estimated with scikit-learn + and allows to flexibly specify kernels and hyperparameters or let them be + optimized automatically. The distance correlation test is implemented with + cython. Here the null distribution is not analytically available, but can be + precomputed with the function generate_and_save_nulldists(...) which saves a + \*.npz file containing the null distribution for different sample sizes. + This file can then be supplied as null_dist_filename. + + Notes + ----- + + GPDC is based on a Gaussian process (GP) regression and a distance + correlation test on the residuals. Distance correlation is described in + [2]_. To test :math:`X \perp Y | Z`, first :math:`Z` is regressed out from + :math:`X` and :math:`Y` assuming the model + + .. math:: X & = f_X(Z) + \epsilon_{X} \\ + Y & = f_Y(Z) + \epsilon_{Y} \\ + \epsilon_{X,Y} &\sim \mathcal{N}(0, \sigma^2) + + using GP regression. Here :math:`\sigma^2` and the kernel bandwidth are + optimzed using ``sklearn``. Then the residuals are transformed to uniform + marginals yielding :math:`r_X,r_Y` and their dependency is tested with + + .. math:: \mathcal{R}\left(r_X, r_Y\right) + + The null distribution of the distance correlation should be pre-computed. + Otherwise it is computed during runtime. + + The cython-code for distance correlation is Copyright (c) 2012, Florian + Finkernagel (https://gist.github.com/ffinkernagel/2960386). + + References + ---------- + .. [2] Gabor J. Szekely, Maria L. Rizzo, and Nail K. Bakirov: Measuring and + testing dependence by correlation of distances, + https://arxiv.org/abs/0803.4101 + + Parameters + ---------- + null_dist_filename : str, otional (default: None) + Path to file containing null distribution. + + gp_version : {'new', 'old'}, optional (default: 'new') + The older GP version from scikit-learn 0.17 was used for the numerical + simulations in [1]_. The newer version from scikit-learn 0.19 is faster + and allows more flexibility regarding kernels etc. + + gp_params : dictionary, optional (default: None) + Dictionary with parameters for ``GaussianProcessRegressor``. + + **kwargs : + Arguments passed on to parent class GaussProcReg. + + """ + @property + def measure(self): + """ + Concrete property to return the measure of the independence test + """ + return self._measure + + def __init__(self, + null_dist_filename=None, + gp_version='new', + gp_params=None, + **kwargs): + self._measure = 'gp_dc' + self.two_sided = False + self.residual_based = True + # Call the parent constructor + CondIndTest.__init__(self, **kwargs) + # Build the regressor + self.gauss_pr = GaussProcReg(self.sig_samples, + self, + gp_version=gp_version, + gp_params=gp_params, + null_dist_filename=null_dist_filename, + verbosity=self.verbosity) + + if self.verbosity > 0: + print("null_dist_filename = %s" % self.gauss_pr.null_dist_filename) + print("gp_version = %s" % self.gauss_pr.gp_version) + if self.gauss_pr.gp_params is not None: + for key in list(self.gauss_pr.gp_params): + print("%s = %s" % (key, self.gauss_pr.gp_params[key])) + print("") + + def _load_nulldist(self, filename): + r""" + Load a precomputed null distribution from a \*.npz file. This + distribution can be calculated using generate_and_save_nulldists(...). + + Parameters + ---------- + filename : strng + Path to the \*.npz file + + Returns + ------- + null_dists, null_samples : dict, int + The null distirbution as a dictionary of distributions keyed by + sample size, the number of null samples in total. + """ + return self.gauss_pr._load_nulldist(filename) + +
[docs] def generate_nulldist(self, df, add_to_null_dists=True): + """Generates null distribution for pairwise independence tests. + + Generates the null distribution for sample size df. Assumes pairwise + samples transformed to uniform marginals. Uses get_dependence_measure + available in class and generates self.sig_samples random samples. Adds + the null distributions to self.gauss_pr.null_dists. + + Parameters + ---------- + df : int + Degrees of freedom / sample size to generate null distribution for. + + add_to_null_dists : bool, optional (default: True) + Whether to add the null dist to the dictionary of null dists or + just return it. + + Returns + ------- + null_dist : array of shape [df,] + Only returned,if add_to_null_dists is False. + """ + return self.gauss_pr._generate_nulldist(df, add_to_null_dists)
+ +
[docs] def generate_and_save_nulldists(self, sample_sizes, null_dist_filename): + """Generates and saves null distribution for pairwise independence + tests. + + Generates the null distribution for different sample sizes. Calls + generate_nulldist. Null dists are saved to disk as + self.null_dist_filename.npz. Also adds the null distributions to + self.gauss_pr.null_dists. + + Parameters + ---------- + sample_sizes : list + List of sample sizes. + + null_dist_filename : str + Name to save file containing null distributions. + """ + self.gauss_pr._generate_and_save_nulldists(sample_sizes, + null_dist_filename)
+ + def _get_single_residuals(self, array, target_var, + return_means=False, + standardize=True, + return_likelihood=False): + """Returns residuals of Gaussian process regression. + + Performs a GP regression of the variable indexed by target_var on the + conditions Z. Here array is assumed to contain X and Y as the first two + rows with the remaining rows (if present) containing the conditions Z. + Optionally returns the estimated mean and the likelihood. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + target_var : {0, 1} + Variable to regress out conditions from. + + standardize : bool, optional (default: True) + Whether to standardize the array beforehand. + + return_means : bool, optional (default: False) + Whether to return the estimated regression line. + + return_likelihood : bool, optional (default: False) + Whether to return the log_marginal_likelihood of the fitted GP + + Returns + ------- + resid [, mean, likelihood] : array-like + The residual of the regression and optionally the estimated mean + and/or the likelihood. + """ + return self.gauss_pr._get_single_residuals( + array, target_var, + return_means, + standardize, + return_likelihood) + +
[docs] def get_model_selection_criterion(self, j, parents, tau_max=0): + """Returns log marginal likelihood for GP regression. + + Fits a GP model of the parents to variable j and returns the negative + log marginal likelihood as a model selection score. Is used to determine + optimal hyperparameters in PCMCI, in particular the pc_alpha value. + + Parameters + ---------- + j : int + Index of target variable in data array. + + parents : list + List of form [(0, -1), (3, -2), ...] containing parents. + + tau_max : int, optional (default: 0) + Maximum time lag. This may be used to make sure that estimates for + different lags in X, Z, all have the same sample size. + + Returns: + score : float + Model score. + """ + return self.gauss_pr._get_model_selection_criterion(j, parents, tau_max)
+ +
[docs] def get_dependence_measure(self, array, xyz): + """Return GPDC measure. + + Estimated as the distance correlation of the residuals of a GP + regression. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + Returns + ------- + val : float + GPDC test statistic. + """ + + x_vals = self._get_single_residuals(array, target_var=0) + y_vals = self._get_single_residuals(array, target_var=1) + val = self._get_dcorr(np.array([x_vals, y_vals])) + return val
+ + + def _get_dcorr(self, array_resid): + """Return distance correlation coefficient. + + The variables are transformed to uniform marginals using the empirical + cumulative distribution function beforehand. Here the null distribution + is not analytically available, but can be precomputed with the function + generate_and_save_nulldists(...) which saves a \*.npz file containing + the null distribution for different sample sizes. This file can then be + supplied as null_dist_filename. + + Parameters + ---------- + array_resid : array-like + data array must be of shape (2, T) + + Returns + ------- + val : float + Distance correlation coefficient. + """ + # Remove ties before applying transformation to uniform marginals + # array_resid = self._remove_ties(array_resid, verbosity=4) + x_vals, y_vals = self._trafo2uniform(array_resid) + + _, val, _, _ = tigramite_cython_code.dcov_all(x_vals, y_vals) + return val + +
[docs] def get_shuffle_significance(self, array, xyz, value, + return_null_dist=False): + """Returns p-value for shuffle significance test. + + For residual-based test statistics only the residuals are shuffled. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + value : number + Value of test statistic for unshuffled estimate. + + Returns + ------- + pval : float + p-value + """ + + x_vals = self._get_single_residuals(array, target_var=0) + y_vals = self._get_single_residuals(array, target_var=1) + array_resid = np.array([x_vals, y_vals]) + xyz_resid = np.array([0, 1]) + + null_dist = self._get_shuffle_dist(array_resid, xyz_resid, + self.get_dependence_measure, + sig_samples=self.sig_samples, + sig_blocklength=self.sig_blocklength, + verbosity=self.verbosity) + + pval = (null_dist >= value).mean() + + if return_null_dist: + return pval, null_dist + return pval
+ +
[docs] def get_analytic_significance(self, value, T, dim): + """Returns p-value for the distance correlation coefficient. + + The null distribution for necessary degrees of freedom (df) is loaded. + If not available, the null distribution is generated with the function + generate_nulldist(). It is recommended to generate the nulldists for a + wide range of sample sizes beforehand with the function + generate_and_save_nulldists(...). The distance correlation coefficient + is one-sided. If the degrees of freedom are less than 1, numpy.nan is + returned. + + Parameters + ---------- + value : float + Test statistic value. + + T : int + Sample length + + dim : int + Dimensionality, ie, number of features. + + Returns + ------- + pval : float or numpy.nan + P-value. + """ + + # GP regression approximately doesn't cost degrees of freedom + df = T + + if df < 1: + pval = np.nan + else: + # idx_near = (np.abs(self.sample_sizes - df)).argmin() + if int(df) not in list(self.gauss_pr.null_dists): + # if np.abs(self.sample_sizes[idx_near] - df) / float(df) > 0.01: + if self.verbosity > 0: + print("Null distribution for GPDC not available " + "for deg. of freed. = %d." % df) + self.generate_nulldist(df) + null_dist_here = self.gauss_pr.null_dists[int(df)] + pval = np.mean(null_dist_here > np.abs(value)) + return pval
+ +
[docs]class CMIknn(CondIndTest): + r"""Conditional mutual information test based on nearest-neighbor estimator. + + Conditional mutual information is the most general dependency measure coming + from an information-theoretic framework. It makes no assumptions about the + parametric form of the dependencies by directly estimating the underlying + joint density. The test here is based on the estimator in S. Frenzel and B. + Pompe, Phys. Rev. Lett. 99, 204101 (2007), combined with a shuffle test to + generate the distribution under the null hypothesis of independence first + used in [3]_. The knn-estimator is suitable only for variables taking a + continuous range of values. For discrete variables use the CMIsymb class. + + Notes + ----- + CMI is given by + + .. math:: I(X;Y|Z) &= \int p(z) \iint p(x,y|z) \log + \frac{ p(x,y |z)}{p(x|z)\cdot p(y |z)} \,dx dy dz + + Its knn-estimator is given by + + .. math:: \widehat{I}(X;Y|Z) &= \psi (k) + \frac{1}{T} \sum_{t=1}^T + \left[ \psi(k_{Z,t}) - \psi(k_{XZ,t}) - \psi(k_{YZ,t}) \right] + + where :math:`\psi` is the Digamma function. This estimator has as a + parameter the number of nearest-neighbors :math:`k` which determines the + size of hyper-cubes around each (high-dimensional) sample point. Then + :math:`k_{Z,},k_{XZ},k_{YZ}` are the numbers of neighbors in the respective + subspaces. + + :math:`k` can be viewed as a density smoothing parameter (although it is + data-adaptive unlike fixed-bandwidth estimators). For large :math:`k`, the + underlying dependencies are more smoothed and CMI has a larger bias, + but lower variance, which is more important for significance testing. Note + that the estimated CMI values can be slightly negative while CMI is a non- + negative quantity. + + This method requires the scipy.spatial.cKDTree package and the tigramite + cython module. + + References + ---------- + .. [3] J. Runge (2018): Conditional Independence Testing Based on a + Nearest-Neighbor Estimator of Conditional Mutual Information. + In Proceedings of the 21st International Conference on Artificial + Intelligence and Statistics. + http://proceedings.mlr.press/v84/runge18a.html + + Parameters + ---------- + knn : int or float, optional (default: 0.2) + Number of nearest-neighbors which determines the size of hyper-cubes + around each (high-dimensional) sample point. If smaller than 1, this is + computed as a fraction of T, hence knn=knn*T. For knn larger or equal to + 1, this is the absolute number. + + shuffle_neighbors : int, optional (default: 10) + Number of nearest-neighbors within Z for the shuffle surrogates which + determines the size of hyper-cubes around each (high-dimensional) sample + point. + + transform : {'ranks', 'standardize', 'uniform', False}, optional + (default: 'ranks') + Whether to transform the array beforehand by standardizing + or transforming to uniform marginals. + + n_jobs : int (optional, default = -1) + Number of jobs to schedule for parallel processing. If -1 is given + all processors are used. Default: 1. + + significance : str, optional (default: 'shuffle_test') + Type of significance test to use. For CMIknn only 'fixed_thres' and + 'shuffle_test' are available. + + **kwargs : + Arguments passed on to parent class CondIndTest. + """ + @property + def measure(self): + """ + Concrete property to return the measure of the independence test + """ + return self._measure + + def __init__(self, + knn=0.2, + shuffle_neighbors=5, + significance='shuffle_test', + transform='ranks', + n_jobs=-1, + **kwargs): + # Set the member variables + self.knn = knn + self.shuffle_neighbors = shuffle_neighbors + self.transform = transform + self._measure = 'cmi_knn' + self.two_sided = False + self.residual_based = False + self.recycle_residuals = False + self.n_jobs = n_jobs + # Call the parent constructor + CondIndTest.__init__(self, significance=significance, **kwargs) + # Print some information about construction + if self.verbosity > 0: + if self.knn < 1: + print("knn/T = %s" % self.knn) + else: + print("knn = %s" % self.knn) + print("shuffle_neighbors = %d\n" % self.shuffle_neighbors) + + def _get_nearest_neighbors(self, array, xyz, knn): + """Returns nearest neighbors according to Frenzel and Pompe (2007). + + Retrieves the distances eps to the k-th nearest neighbors for every + sample in joint space XYZ and returns the numbers of nearest neighbors + within eps in subspaces Z, XZ, YZ. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + knn : int or float + Number of nearest-neighbors which determines the size of hyper-cubes + around each (high-dimensional) sample point. If smaller than 1, this + is computed as a fraction of T, hence knn=knn*T. For knn larger or + equal to 1, this is the absolute number. + + Returns + ------- + k_xz, k_yz, k_z : tuple of arrays of shape (T,) + Nearest neighbors in subspaces. + """ + + dim, T = array.shape + array = array.astype('float') + + # Add noise to destroy ties... + array += (1E-6 * array.std(axis=1).reshape(dim, 1) + * np.random.rand(array.shape[0], array.shape[1])) + + if self.transform == 'standardize': + # Standardize + array = array.astype('float') + array -= array.mean(axis=1).reshape(dim, 1) + array /= array.std(axis=1).reshape(dim, 1) + # FIXME: If the time series is constant, return nan rather than + # raising Exception + if np.isnan(array).sum() != 0: + raise ValueError("nans after standardizing, " + "possibly constant array!") + elif self.transform == 'uniform': + array = self._trafo2uniform(array) + elif self.transform == 'ranks': + array = array.argsort(axis=1).argsort(axis=1).astype('float') + + + # Use cKDTree to get distances eps to the k-th nearest neighbors for + # every sample in joint space XYZ with maximum norm + tree_xyz = spatial.cKDTree(array.T) + epsarray = tree_xyz.query(array.T, k=knn+1, p=np.inf, + eps=0., n_jobs=self.n_jobs)[0][:, knn].astype('float') + + # Prepare for fast cython access + dim_x = int(np.where(xyz == 0)[0][-1] + 1) + dim_y = int(np.where(xyz == 1)[0][-1] + 1 - dim_x) + + k_xz, k_yz, k_z = \ + tigramite_cython_code._get_neighbors_within_eps_cython(array, + T, + dim_x, + dim_y, + epsarray, + knn, + dim) + return k_xz, k_yz, k_z + +
[docs] def get_dependence_measure(self, array, xyz): + """Returns CMI estimate as described in Frenzel and Pompe PRL (2007). + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + Returns + ------- + val : float + Conditional mutual information estimate. + """ + + dim, T = array.shape + + if self.knn < 1: + knn_here = max(1, int(self.knn*T)) + else: + knn_here = max(1, int(self.knn)) + + k_xz, k_yz, k_z = self._get_nearest_neighbors(array=array, + xyz=xyz, + knn=knn_here) + + val = special.digamma(knn_here) - (special.digamma(k_xz) + + special.digamma(k_yz) - + special.digamma(k_z)).mean() + + return val
+ + +
[docs] def get_shuffle_significance(self, array, xyz, value, + return_null_dist=False): + """Returns p-value for nearest-neighbor shuffle significance test. + + For non-empty Z, overwrites get_shuffle_significance from the parent + class which is a block shuffle test, which does not preserve + dependencies of X and Y with Z. Here the parameter shuffle_neighbors is + used to permute only those values :math:`x_i` and :math:`x_j` for which + :math:`z_j` is among the nearest niehgbors of :math:`z_i`. If Z is + empty, the block-shuffle test is used. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + value : number + Value of test statistic for unshuffled estimate. + + Returns + ------- + pval : float + p-value + """ + dim, T = array.shape + + # Skip shuffle test if value is above threshold + # if value > self.minimum threshold: + # if return_null_dist: + # return 0., None + # else: + # return 0. + + # max_neighbors = max(1, int(max_neighbor_ratio*T)) + x_indices = np.where(xyz == 0)[0] + z_indices = np.where(xyz == 2)[0] + + if len(z_indices) > 0 and self.shuffle_neighbors < T: + if self.verbosity > 2: + print(" nearest-neighbor shuffle significance " + "test with n = %d and %d surrogates" % ( + self.shuffle_neighbors, self.sig_samples)) + + # Get nearest neighbors around each sample point in Z + z_array = np.fastCopyAndTranspose(array[z_indices, :]) + tree_xyz = spatial.cKDTree(z_array) + neighbors = tree_xyz.query(z_array, + k=self.shuffle_neighbors, + p=np.inf, + eps=0.)[1].astype('int32') + + null_dist = np.zeros(self.sig_samples) + for sam in range(self.sig_samples): + + # Generate random order in which to go through indices loop in + # next step + order = np.random.permutation(T).astype('int32') + # print(order[:5]) + # Select a series of neighbor indices that contains as few as + # possible duplicates + restricted_permutation = \ + tigramite_cython_code._get_restricted_permutation_cython( + T=T, + shuffle_neighbors=self.shuffle_neighbors, + neighbors=neighbors, + order=order) + + array_shuffled = np.copy(array) + for i in x_indices: + array_shuffled[i] = array[i, restricted_permutation] + + null_dist[sam] = self.get_dependence_measure(array_shuffled, + xyz) + + else: + null_dist = \ + self._get_shuffle_dist(array, xyz, + self.get_dependence_measure, + sig_samples=self.sig_samples, + sig_blocklength=self.sig_blocklength, + verbosity=self.verbosity) + + # Sort + null_dist.sort() + pval = (null_dist >= value).mean() + + if return_null_dist: + return pval, null_dist + return pval
+ + +
[docs]class CMIsymb(CondIndTest): + r"""Conditional mutual information test based on discrete estimator. + + Conditional mutual information is the most general dependency measure + coming from an information-theoretic framework. It makes no assumptions + about the parametric form of the dependencies by directly estimating the + underlying joint density. The test here is based on directly estimating + the joint distribution assuming symbolic input, combined with a + shuffle test to generate the distribution under the null hypothesis of + independence. The knn-estimator is suitable only for discrete variables. + For continuous variables, either pre-process the data using the functions + in data_processing or, better, use the CMIknn class. + + Notes + ----- + CMI and its estimator are given by + + .. math:: I(X;Y|Z) &= \sum p(z) \sum \sum p(x,y|z) \log + \frac{ p(x,y |z)}{p(x|z)\cdot p(y |z)} \,dx dy dz + + Parameters + ---------- + n_symbs : int, optional (default: None) + Number of symbols in input data. Should be at least as large as the + maximum array entry + 1. If None, n_symbs is based on the + maximum value in the array (array.max() + 1). + + significance : str, optional (default: 'shuffle_test') + Type of significance test to use. For CMIsymb only 'fixed_thres' and + 'shuffle_test' are available. + + sig_blocklength : int, optional (default: 1) + Block length for block-shuffle significance test. + + conf_blocklength : int, optional (default: 1) + Block length for block-bootstrap. + + **kwargs : + Arguments passed on to parent class CondIndTest. + """ + @property + def measure(self): + """ + Concrete property to return the measure of the independence test + """ + return self._measure + + def __init__(self, + n_symbs=None, + significance='shuffle_test', + sig_blocklength=1, + conf_blocklength=1, + **kwargs): + # Setup the member variables + self._measure = 'cmi_symb' + self.two_sided = False + self.residual_based = False + self.recycle_residuals = False + self.n_symbs = n_symbs + # Call the parent constructor + CondIndTest.__init__(self, + significance=significance, + sig_blocklength=sig_blocklength, + conf_blocklength=conf_blocklength, + **kwargs) + + if self.verbosity > 0: + print("n_symbs = %s" % self.n_symbs) + print("") + + if self.conf_blocklength is None or self.sig_blocklength is None: + warnings.warn("Automatic block-length estimations from decay of " + "autocorrelation may not be sensical for discrete " + "data") + + def _bincount_hist(self, symb_array, weights=None): + """Computes histogram from symbolic array. + + The maximum of the symbolic array determines the alphabet / number + of bins. + + Parameters + ---------- + symb_array : integer array + Data array of shape (dim, T). + + weights : float array, optional (default: None) + Optional weights array of shape (dim, T). + + Returns + ------- + hist : array + Histogram array of shape (base, base, base, ...)*number of + dimensions with Z-dimensions coming first. + """ + + if self.n_symbs is None: + n_symbs = int(symb_array.max() + 1) + else: + n_symbs = self.n_symbs + if n_symbs < int(symb_array.max() + 1): + raise ValueError("n_symbs must be >= symb_array.max() + 1 = {}".format(symb_array.max() + 1)) + + if 'int' not in str(symb_array.dtype): + raise ValueError("Input data must of integer type, where each " + "number indexes a symbol.") + + dim, T = symb_array.shape + + flathist = np.zeros((n_symbs ** dim), dtype='int16') + multisymb = np.zeros(T, dtype='int64') + if weights is not None: + flathist = np.zeros((n_symbs ** dim), dtype='float32') + multiweights = np.ones(T, dtype='float32') + + for i in range(dim): + multisymb += symb_array[i, :] * n_symbs ** i + if weights is not None: + multiweights *= weights[i, :] + + if weights is None: + result = np.bincount(multisymb) + else: + result = (np.bincount(multisymb, weights=multiweights) + / multiweights.sum()) + + flathist[:len(result)] += result + + hist = flathist.reshape(tuple([n_symbs, n_symbs] + + [n_symbs for i in range(dim - 2)])).T + + return hist + +
[docs] def get_dependence_measure(self, array, xyz): + """Returns CMI estimate based on bincount histogram. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + Returns + ------- + val : float + Conditional mutual information estimate. + """ + + _, T = array.shape + + # High-dimensional histogram + hist = self._bincount_hist(array, weights=None) + + def _plogp_vector(T): + """Precalculation of p*log(p) needed for entropies.""" + gfunc = np.zeros(T + 1) + data = np.arange(1, T + 1, 1) + gfunc[1:] = data * np.log(data) + def plogp_func(time): + return gfunc[time] + return np.vectorize(plogp_func) + + plogp = _plogp_vector(T) + hxyz = (-(plogp(hist)).sum() + plogp(T)) / float(T) + hxz = (-(plogp(hist.sum(axis=1))).sum() + plogp(T)) / float(T) + hyz = (-(plogp(hist.sum(axis=0))).sum() + plogp(T)) / float(T) + hz = (-(plogp(hist.sum(axis=0).sum(axis=0))).sum()+plogp(T)) / float(T) + val = hxz + hyz - hz - hxyz + return val
+ +
[docs] def get_shuffle_significance(self, array, xyz, value, + return_null_dist=False): + """Returns p-value for shuffle significance test. + + For residual-based test statistics only the residuals are shuffled. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + value : number + Value of test statistic for unshuffled estimate. + + Returns + ------- + pval : float + p-value + """ + + null_dist = self._get_shuffle_dist(array, xyz, + self.get_dependence_measure, + sig_samples=self.sig_samples, + sig_blocklength=self.sig_blocklength, + verbosity=self.verbosity) + + pval = (null_dist >= value).mean() + + if return_null_dist: + return pval, null_dist + return pval
+ +
[docs]class RCOT(CondIndTest): + r"""Randomized Conditional Correlation Test. + + Tests conditional independence in the fully non-parametric setting based on + Kernel measures. For not too small sample sizes, the test can utilize an + analytic approximation of the null distribution making it very fast. Based + on r-package ``rcit``. This test is described in [5]_. + + Notes + ----- + + RCOT is a fast variant of the Kernel Conditional Independence Test (KCIT) + utilizing random Fourier features. Kernel tests measure conditional + independence in the fully non-parametric setting. In practice, RCOT tests + scale linearly with sample size and return accurate p-values much faster + than KCIT in the large sample size context. To use the analytical null + approximation, the sample size should be at least ~1000. + + The method is fully described in [5]_ and the r-package documentation. The + free parameters are the approximation of the partial kernel cross-covariance + matrix and the number of random fourier features for the conditioning set. + One caveat is that RCOT is, as the name suggests, based on random fourier + features. To get reproducable results, you should fix the seed (default). + + This class requires the rpy package and the prior installation of ``rcit`` + from https://github.com/ericstrobl/RCIT. This is provided with tigramite + as an external package. + + References + ---------- + .. [5] Eric V. Strobl, Kun Zhang, Shyam Visweswaran: + Approximate Kernel-based Conditional Independence Tests for Fast Non- + Parametric Causal Discovery. + https://arxiv.org/abs/1702.03877 + + Parameters + ---------- + num_f : int, optional + Number of random fourier features for conditioning set. More features + better approximate highly structured joint densities, but take more + computational time. + + approx : str, optional + Which approximation of the partial cross-covariance matrix, options: + 'lpd4' the Lindsay-Pilla-Basak method (default), 'gamma' for the + Satterthwaite-Welch method, 'hbe' for the Hall-Buckley-Eagleson method, + 'chi2' for a normalized chi-squared statistic, 'perm' for permutation + testing (warning: this one is slow). + + seed : int or None, optional + Which random fourier feature seed to use. If None, you won't get + reproducable results. + + significance : str, optional (default: 'analytic') + Type of significance test to use. + + **kwargs : + Arguments passed on to parent class CondIndTest. + """ + @property + def measure(self): + """ + Concrete property to return the measure of the independence test + """ + return self._measure + + def __init__(self, + num_f=25, + approx="lpd4", + seed=42, + significance='analytic', + **kwargs): + # Set the members + self.num_f = num_f + self.approx = approx + self.seed = seed + self._measure = 'rcot' + self.two_sided = False + self.residual_based = False + self._pval = None + # Call the parent constructor + CondIndTest.__init__(self, significance=significance, **kwargs) + + # Print some information + if self.verbosity > 0: + print("num_f = %s" % self.num_f + "\n") + print("approx = %s" % self.approx + "\n\n") + +
[docs] def get_dependence_measure(self, array, xyz): + """Returns RCOT estimate. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + Returns + ------- + val : float + RCOT estimate. + """ + dim, T = array.shape + x_vals = array[0] + y_vals = array[1] + z_vals = np.fastCopyAndTranspose(array[2:]) + + rcot = rpy2.robjects.r['RCoT'](x_vals, y_vals, z_vals, + num_f=self.num_f, + approx=self.approx, + seed=self.seed) + + val = float(rcot.rx2('Sta')[0]) + # Cache the p-value for use later + self._pval = float(rcot.rx2('p')[0]) + + return val
+ +
[docs] def get_analytic_significance(self, **args): + """ + Returns analytic p-value from RCoT test statistic. + NOTE: Must first run get_dependence_measure, where p-value is determined + from RCoT test statistic. + + Returns + ------- + pval : float or numpy.nan + P-value. + """ + return self._pval
+ +
[docs] def get_shuffle_significance(self, array, xyz, value, + return_null_dist=False): + """Returns p-value for shuffle significance test. + + For residual-based test statistics only the residuals are shuffled. + + Parameters + ---------- + array : array-like + data array with X, Y, Z in rows and observations in columns + + xyz : array of ints + XYZ identifier array of shape (dim,). + + value : number + Value of test statistic for unshuffled estimate. + + Returns + ------- + pval : float + p-value + """ + + null_dist = self._get_shuffle_dist(array, xyz, + self.get_dependence_measure, + sig_samples=self.sig_samples, + sig_blocklength=self.sig_blocklength, + verbosity=self.verbosity) + + pval = (null_dist >= value).mean() + + if return_null_dist: + return pval, null_dist + return pval
+
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/tigramite/models.html b/docs/_build/html/_modules/tigramite/models.html new file mode 100644 index 00000000..2e847f1f --- /dev/null +++ b/docs/_build/html/_modules/tigramite/models.html @@ -0,0 +1,1279 @@ + + + + + + + tigramite.models — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

Source code for tigramite.models

+"""Tigramite causal discovery for time series."""
+
+# Author: Jakob Runge <jakob@jakob-runge.com>
+#
+# License: GNU General Public License v3.0
+
+from __future__ import print_function
+from copy import deepcopy
+
+import numpy as np
+
+from tigramite.data_processing import DataFrame
+from tigramite.pcmci import PCMCI
+
+try:
+    import sklearn
+    import sklearn.linear_model
+except:
+    print("Could not import sklearn...")
+
+try:
+    import networkx
+except:
+    print("Could not import networkx, LinearMediation plots not possible...")
+
+
+
+
[docs]class Models(): + """Base class for time series models. + + Allows to fit any model from sklearn to the parents of a target variable. + Also takes care of missing values, masking and preprocessing. + + Parameters + ---------- + dataframe : data object + Tigramite dataframe object. It must have the attributes dataframe.values + yielding a numpy array of shape (observations T, variables N) and + optionally a mask of the same shape and a missing values flag. + + model : sklearn model object + For example, sklearn.linear_model.LinearRegression() for a linear + regression model. + + data_transform : sklearn preprocessing object, optional (default: None) + Used to transform data prior to fitting. For example, + sklearn.preprocessing.StandardScaler for simple standardization. The + fitted parameters are stored. + + mask_type : {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence measure + I(X; Y | Z) the samples should be masked. If None, 'y' is used, which + excludes all time slices containing masked samples in Y. Explained in + [1]_. + + verbosity : int, optional (default: 0) + Level of verbosity. + """ + + def __init__(self, + dataframe, + model, + data_transform=sklearn.preprocessing.StandardScaler(), + mask_type=None, + verbosity=0): + # Set the mask type and dataframe object + self.mask_type = mask_type + self.dataframe = dataframe + # Get the number of nodes for this dataset + self.N = self.dataframe.values.shape[1] + # Set the model to be used + self.model = model + # Set the data_transform object and verbosity + self.data_transform = data_transform + self.verbosity = verbosity + # Initialize the object that will be set later + self.all_parents = None + self.selected_variables = None + self.tau_max = None + self.fit_results = None + +
[docs] def get_fit(self, all_parents, + selected_variables=None, + tau_max=None, + cut_off='max_lag_or_tau_max', + return_data=False): + """Fit time series model. + + For each variable in selected_variables, the sklearn model is fitted + with :math:`y` given by the target variable, and :math:`X` given by its + parents. The fitted model class is returned for later use. + + Parameters + ---------- + all_parents : dictionary + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} containing + the parents estimated with PCMCI. + + selected_variables : list of integers, optional (default: range(N)) + Specify to estimate parents only for selected variables. If None is + passed, parents are estimated for all variables. + + tau_max : int, optional (default: None) + Maximum time lag. If None, the maximum lag in all_parents is used. + + cut_off : {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'} + How many samples to cutoff at the beginning. The default is + 'max_lag_or_tau_max', which uses the maximum of tau_max and the + conditions. This is useful to compare multiple models on the same + sample. Other options are '2xtau_max', which guarantees that MCI + tests are all conducted on the same samples. For modeling, can be + used, Last, 'max_lag' uses as much samples as possible. + + return_data : bool, optional (default: False) + Whether to save the data array. + + Returns + ------- + fit_results : dictionary of sklearn model objects for each variable + Returns the sklearn model after fitting. Also returns the data + transformation parameters. + + """ + # Initialize the fit by setting the instance's all_parents attribute + self.all_parents = all_parents + # Set the default selected variables to all variables and check if this + # should be overwritten + self.selected_variables = range(self.N) + if selected_variables is not None: + self.selected_variables = selected_variables + # Find the maximal parents lag + max_parents_lag = 0 + for j in self.selected_variables: + if all_parents[j]: + this_parent_lag = np.abs(np.array(all_parents[j])[:, 1]).max() + max_parents_lag = max(max_parents_lag, this_parent_lag) + # Set the default tau max and check if it shoudl be overwritten + self.tau_max = max_parents_lag + if tau_max is not None: + self.tau_max = tau_max + if self.tau_max < max_parents_lag: + raise ValueError("tau_max = %d, but must be at least " + " max_parents_lag = %d" + "" % (self.tau_max, max_parents_lag)) + # Initialize the fit results + fit_results = {} + for j in self.selected_variables: + Y = [(j, 0)] + X = [(j, 0)] # dummy + Z = self.all_parents[j] + array, xyz = \ + self.dataframe.construct_array(X, Y, Z, + tau_max=self.tau_max, + mask_type=self.mask_type, + cut_off=cut_off, + verbosity=self.verbosity) + # Get the dimensions out of the constructed array + dim, T = array.shape + dim_z = dim - 2 + # Transform the data if needed + if self.data_transform is not None: + array = self.data_transform.fit_transform(X=array.T).T + # Fit the model if there are any parents for this variable to fit + if dim_z > 0: + # Copy and fit the model + a_model = deepcopy(self.model) + a_model.fit(X=array[2:].T, y=array[1]) + # Cache the results + fit_results[j] = {} + fit_results[j]['model'] = a_model + # Cache the data transform + fit_results[j]['data_transform'] = deepcopy(self.data_transform) + # Cache the data if needed + if return_data: + fit_results[j]['data'] = array + # If there are no parents, skip this variable + else: + fit_results[j] = None + + # Cache and return the fit results + self.fit_results = fit_results + return fit_results
+ +
[docs] def get_coefs(self): + """Returns dictionary of coefficients for linear models. + + Only for models from sklearn.linear_model + + Returns + ------- + coeffs : dictionary + Dictionary of dictionaries for each variable with keys given by the + parents and the regression coefficients as values. + """ + coeffs = {} + for j in self.selected_variables: + coeffs[j] = {} + for ipar, par in enumerate(self.all_parents[j]): + coeffs[j][par] = self.fit_results[j]['model'].coef_[ipar] + return coeffs
+ + +
[docs]class LinearMediation(Models): + r"""Linear mediation analysis for time series models. + + Fits linear model to parents and provides functions to return measures such + as causal effect, mediated causal effect, average causal effect, etc. as + described in [4]_. + + Notes + ----- + This class implements the following causal mediation measures introduced in + [4]_: + + * causal effect (CE) + * mediated causal effect (MCE) + * average causal effect (ACE) + * average causal susceptibility (ACS) + * average mediated causal effect (AMCE) + + Consider a simple model of a causal chain as given in the Example with + + .. math:: X_t &= \eta^X_t \\ + Y_t &= 0.5 X_{t-1} + \eta^Y_t \\ + Z_t &= 0.5 Y_{t-1} + \eta^Z_t + + Here the link coefficient of :math:`X_{t-2} \to Z_t` is zero while the + causal effect is 0.25. MCE through :math:`Y` is 0.25 implying that *all* + of the the CE is explained by :math:`Y`. ACE from :math:`X` is 0.37 since it + has CE 0.5 on :math:`Y` and 0.25 on :math:`Z`. + + Examples + -------- + >>> numpy.random.seed(42) + >>> links_coeffs = {0: [], 1: [((0, -1), 0.5)], 2: [((1, -1), 0.5)]} + >>> data, true_parents = pp.var_process(links_coeffs, T=1000) + >>> dataframe = pp.DataFrame(data) + >>> med = LinearMediation(dataframe=dataframe) + >>> med.fit_model(all_parents=true_parents, tau_max=3) + >>> print "Link coefficient (0, -2) --> 2: ", med.get_coeff(i=0, tau=-2, j=2) + >>> print "Causal effect (0, -2) --> 2: ", med.get_ce(i=0, tau=-2, j=2) + >>> print "Mediated Causal effect (0, -2) --> 2 through 1: ", med.get_mce(i=0, tau=-2, j=2, k=1) + >>> print "Average Causal Effect: ", med.get_all_ace() + >>> print "Average Causal Susceptibility: ", med.get_all_acs() + >>> print "Average Mediated Causal Effect: ", med.get_all_amce() + Link coefficient (0, -2) --> 2: 0.0 + Causal effect (0, -2) --> 2: 0.250648072987 + Mediated Causal effect (0, -2) --> 2 through 1: 0.250648072987 + Average Causal Effect: [ 0.36897445 0.25718002 0. ] + Average Causal Susceptibility: [ 0. 0.24365041 0.38250406] + Average Mediated Causal Effect: [ 0. 0.12532404 0. ] + + References + ---------- + .. [4] J. Runge et al. (2015): Identifying causal gateways and mediators in + complex spatio-temporal systems. + Nature Communications, 6, 8502. http://doi.org/10.1038/ncomms9502 + + Parameters + ---------- + dataframe : data object + Tigramite dataframe object. It must have the attributes dataframe.values + yielding a numpy array of shape (observations T, variables N) and + optionally a mask of the same shape and a missing values flag. + + model_params : dictionary, optional (default: None) + Optional parameters passed on to sklearn model + + data_transform : sklearn preprocessing object, optional (default: None) + Used to transform data prior to fitting. For example, + sklearn.preprocessing.StandardScaler for simple standardization. The + fitted parameters are stored. + + mask_type : {'y','x','z','xy','xz','yz','xyz'} + Masking mode: Indicators for which variables in the dependence measure + I(X; Y | Z) the samples should be masked. If None, 'y' is used, which + excludes all time slices containing masked samples in Y. Explained in + [1]_. + + verbosity : int, optional (default: 0) + Level of verbosity. + """ + def __init__(self, + dataframe, + model_params=None, + data_transform=sklearn.preprocessing.StandardScaler(), + mask_type=None, + verbosity=0): + # Initialize the member variables to None + self.phi = None + self.psi = None + self.all_psi_k = None + + # Build the model using the parameters + if model_params is None: + model_params = {} + this_model = sklearn.linear_model.LinearRegression(**model_params) + Models.__init__(self, + dataframe=dataframe, + model=this_model, + data_transform=data_transform, + mask_type=mask_type, + verbosity=verbosity) + +
[docs] def fit_model(self, all_parents, tau_max=None): + """Fit linear time series model. + + Fits a sklearn.linear_model.LinearRegression model to the parents of + each variable and computes the coefficient matrices :math:`\Phi` and + :math:`\Psi` as described in [4]_. + + Parameters + ---------- + all_parents : dictionary + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} containing + the parents estimated with PCMCI. + + tau_max : int, optional (default: None) + Maximum time lag. If None, the maximum lag in all_parents is used. + + """ + # Fit the model using the base class + self.fit_results = self.get_fit(all_parents=all_parents, + selected_variables=None, + tau_max=tau_max) + # Cache the results in the member variables + coeffs = self.get_coefs() + self.phi = self._get_phi(coeffs) + self.psi = self._get_psi(self.phi) + self.all_psi_k = self._get_all_psi_k(self.phi)
+ + def _check_sanity(self, X, Y, k=None): + """Checks validity of some parameters.""" + + if len(X) != 1 or len(Y) != 1: + raise ValueError("X must be of form [(i, -tau)] and Y = [(j, 0)], " + "but are X = %s, Y=%s" % (X, Y)) + + i, tau = X[0] + + if abs(tau) > self.tau_max: + raise ValueError("X must be of form [(i, -tau)] with" + " tau <= tau_max") + + if k is not None and (k < 0 or k >= self.N): + raise ValueError("k must be in [0, N)") + + def _get_phi(self, coeffs): + """Returns the linear coefficient matrices for different lags.different + + Parameters + ---------- + coeffs : dictionary + Dictionary of coefficients for each parent. + + Returns + ------- + phi : array-like, shape (tau_max + 1, N, N) + Matrices of coefficients for each time lag. + """ + + phi = np.zeros((self.tau_max + 1, self.N, self.N)) + phi[0] = np.identity(self.N) + + for j in list(coeffs): + for par in list(coeffs[j]): + i, tau = par + phi[abs(tau), j, i] = coeffs[j][par] + + return phi + + def _get_psi(self, phi): + """Returns the linear causal effect matrices for different lags. + + Parameters + ---------- + phi : array-like + Coefficient matrices at different lags. + + Returns + ------- + psi : array-like, shape (tau_max + 1, N, N) + Matrices of causal effects for each time lag. + """ + + psi = np.zeros((self.tau_max + 1, self.N, self.N)) + + psi[0] = np.identity(self.N) + for n in range(1, self.tau_max + 1): + psi[n] = np.zeros((self.N, self.N)) + for s in range(1, n+1): + psi[n] += np.dot(phi[s], psi[n-s]) + + return psi + + def _get_psi_k(self, phi, k): + """Returns the linear causal effect matrices excluding variable k. + + Parameters + ---------- + phi : array-like + Coefficient matrices at different lags. + + k : int + Variable index to exclude causal effects through. + + Returns + ------- + psi_k : array-like, shape (tau_max + 1, N, N) + Matrices of causal effects excluding k. + """ + + psi_k = np.zeros((self.tau_max + 1, self.N, self.N)) + + psi_k[0] = np.identity(self.N) + phi_k = np.copy(phi) + phi_k[1:, k, :] = 0. + for n in range(1, self.tau_max + 1): + psi_k[n] = np.zeros((self.N, self.N)) + for s in range(1, n+1): + psi_k[n] += np.dot(phi_k[s], psi_k[n-s]) + + return psi_k + + def _get_all_psi_k(self, phi): + """Returns the linear causal effect matrices excluding variables. + + Parameters + ---------- + phi : array-like + Coefficient matrices at different lags. + + Returns + ------- + all_psi_k : array-like, shape (N, tau_max + 1, N, N) + Matrices of causal effects where for each row another variable is + excluded. + """ + + all_psi_k = np.zeros((self.N, self.tau_max + 1, self.N, self.N)) + + for k in range(self.N): + all_psi_k[k] = self._get_psi_k(phi, k) + + return all_psi_k + +
[docs] def get_val_matrix(self,): + """Returns the matrix of linear coefficients. + + Format is val_matrix[i, j, tau] denotes coefficient of link + i --tau--> j. + + Returns + ------- + val_matrix : array + Matrix of linear coefficients, shape (N, N, tau_max + 1). + """ + + return self.phi.transpose()
+ + +
[docs] def net_to_tsg(self, row, lag, max_lag): + """Helper function to translate from network to time series graph.""" + return row*max_lag + lag
+ +
[docs] def tsg_to_net(self, node, max_lag): + """Helper function to translate from time series graph to network.""" + row = node // max_lag + lag = node % max_lag + return (row, -lag)
+ +
[docs] def get_tsg(self, link_matrix, val_matrix=None, include_neighbors = False): + """Returns time series graph matrix. + + Constructs a matrix of shape (N*tau_max, N*tau_max) from link_matrix. + This matrix can be used for plotting the time series graph and analyzing + causal pathways. + + link_matrix : bool array-like, optional (default: None) + Matrix of significant links. Must be of same shape as val_matrix. Either + sig_thres or link_matrix has to be provided. + + val_matrix : array_like + Matrix of shape (N, N, tau_max+1) containing test statistic values. + + include_neighbors : bool, optional (default: False) + Whether to include causal paths emanating from neighbors of i + + Returns + ------- + tsg : array of shape (N*tau_max, N*tau_max) + Time series graph matrix. + """ + + N = len(link_matrix) + max_lag = link_matrix.shape[2] + 1 + + # Create TSG + tsg = np.zeros((N*max_lag, N*max_lag)) + for i, j, tau in np.column_stack(np.where(link_matrix)): + if tau > 0 or include_neighbors: + for t in range(max_lag): + link_start = self.net_to_tsg(i, t-tau, max_lag) + link_end = self.net_to_tsg(j, t, max_lag) + if (0 <= link_start and + (link_start % max_lag) <= (link_end % max_lag)): + if val_matrix is not None: + tsg[link_start, link_end] = val_matrix[i,j,tau] + else: + tsg[link_start, link_end] = 1 + return tsg
+ + +
[docs] def get_mediation_graph_data(self, i, tau, j, include_neighbors=False): + r"""Returns link and node weights for mediation analysis. + + Returns array with non-zero entries for links that are on causal + paths between :math:`i` and :math:`j` at lag :math:`\tau`. + ``path_val_matrix`` contains the corresponding path coefficients and + ``path_node_array`` the MCE values. ``tsg_path_val_matrix`` contains the + corresponding values in the time series graph format. + + Parameters + ---------- + i : int + Index of cause variable. + + tau : int + Lag of cause variable. + + j : int + Index of effect variable. + + include_neighbors : bool, optional (default: False) + Whether to include causal paths emanating from neighbors of i + + Returns + ------- + graph_data : dictionary + Dictionary of matrices for coloring mediation graph plots. + + """ + + path_link_matrix = np.zeros((self.N, self.N, self.tau_max + 1)) + path_val_matrix = np.zeros((self.N, self.N, self.tau_max + 1)) + + # Get mediation of path variables + path_node_array = (self.psi.reshape(1, self.tau_max + 1, self.N, self.N) + - self.all_psi_k)[:,abs(tau), j, i] + + # Get involved links + val_matrix = self.phi.transpose() + link_matrix = val_matrix != 0. + + max_lag = link_matrix.shape[2] + 1 + + # include_neighbors = False because True would allow + # --> o -- motifs in networkx.all_simple_paths as paths, but + # these are blocked... + tsg = self.get_tsg(link_matrix, val_matrix=val_matrix, + include_neighbors = False) + + if include_neighbors: + # Add contemporaneous links only at source node + for m, n in zip(*np.where(link_matrix[:,:,0])): + # print m,n + if m != n: + tsg[self.net_to_tsg(m, max_lag-tau-1, max_lag), + self.net_to_tsg(n, max_lag-tau-1, max_lag) + ] = val_matrix[m, n, 0] + + tsg_path_val_matrix = np.zeros(tsg.shape) + + graph = networkx.DiGraph(tsg) + pathways = [] + + for path in networkx.all_simple_paths(graph, + source=self.net_to_tsg(i, max_lag-tau-1, max_lag), + target=self.net_to_tsg(j, max_lag-0-1, max_lag)): + pathways.append([self.tsg_to_net(p, max_lag) for p in path]) + for ip, p in enumerate(path[1:]): + tsg_path_val_matrix[path[ip], p] = tsg[path[ip], p] + + k, tau_k = self.tsg_to_net(p, max_lag) + link_start = self.tsg_to_net(path[ip], max_lag) + link_end = self.tsg_to_net(p, max_lag) + delta_tau = abs(link_end[1] - link_start[1]) + path_val_matrix[link_start[0], + link_end[0], + delta_tau] = val_matrix[link_start[0], + link_end[0], + delta_tau] + + graph_data = {'path_node_array':path_node_array, + 'path_val_matrix':path_val_matrix, + 'tsg_path_val_matrix':tsg_path_val_matrix,} + + return graph_data
+ + +
[docs] def get_coeff(self, i, tau, j): + """Returns link coefficient. + + This is the causal effect for a particular link (i, tau) --> j. + + Parameters + ---------- + i : int + Index of cause variable. + + tau : int + Lag of cause variable. + + j : int + Index of effect variable. + + Returns + ------- + coeff : float + """ + return self.phi[abs(tau), j, i]
+ +
[docs] def get_ce(self, i, tau, j): + """Returns the causal effect. + + This is the causal effect for (i, tau) -- --> j. + + Parameters + ---------- + i : int + Index of cause variable. + + tau : int + Lag of cause variable. + + j : int + Index of effect variable. + + Returns + ------- + ce : float + """ + return self.psi[abs(tau), j, i]
+ +
[docs] def get_ce_max(self, i, j): + """Returns the causal effect. + + This is the maximum absolute causal effect for i --> j across all lags. + + Parameters + ---------- + i : int + Index of cause variable. + + j : int + Index of effect variable. + + Returns + ------- + ce : float + """ + return np.abs(self.psi[1:, j, i]).max()
+ +
[docs] def get_mce(self, i, tau, j, k): + """Returns the mediated causal effect. + + This is the causal effect for i --> j minus the causal effect not going + through k. + + Parameters + ---------- + i : int + Index of cause variable. + + tau : int + Lag of cause variable. + + j : int + Index of effect variable. + + k : int + Index of mediator variable. + + Returns + ------- + mce : float + """ + mce = self.psi[abs(tau), j, i] - self.all_psi_k[k, abs(tau), j, i] + return mce
+ +
[docs] def get_ace(self, i, lag_mode='absmax', exclude_i=True): + """Returns the average causal effect. + + This is the average causal effect (ACE) emanating from variable i to any + other variable. With lag_mode='absmax' this is based on the lag of + maximum CE for each pair. + + Parameters + ---------- + i : int + Index of cause variable. + + lag_mode : {'absmax', 'all_lags'} + Lag mode. Either average across all lags between each pair or only + at the lag of maximum absolute causal effect. + + exclude_i : bool, optional (default: True) + Whether to exclude causal effects on the variable itself at later + lags. + + Returns + ------- + ace :float + Average Causal Effect. + """ + + all_but_i = np.ones(self.N, dtype='bool') + if exclude_i: + all_but_i[i] = False + + if lag_mode == 'absmax': + return np.abs(self.psi[1:, all_but_i, i]).max(axis=0).mean() + elif lag_mode == 'all_lags': + return np.abs(self.psi[1:, all_but_i, i]).mean() + else: + raise ValueError("lag_mode = %s not implemented" % lag_mode)
+ +
[docs] def get_all_ace(self, lag_mode='absmax', exclude_i=True): + """Returns the average causal effect for all variables. + + This is the average causal effect (ACE) emanating from variable i to any + other variable. With lag_mode='absmax' this is based on the lag of + maximum CE for each pair. + + Parameters + ---------- + lag_mode : {'absmax', 'all_lags'} + Lag mode. Either average across all lags between each pair or only + at the lag of maximum absolute causal effect. + + exclude_i : bool, optional (default: True) + Whether to exclude causal effects on the variable itself at later + lags. + + Returns + ------- + ace : array of shape (N,) + Average Causal Effect for each variable. + """ + + ace = np.zeros(self.N) + for i in range(self.N): + ace[i] = self.get_ace(i, lag_mode=lag_mode, exclude_i=exclude_i) + + return ace
+ +
[docs] def get_acs(self, j, lag_mode='absmax', exclude_j=True): + """Returns the average causal susceptibility. + + This is the Average Causal Susceptibility (ACS) affecting a variable j + from any other variable. With lag_mode='absmax' this is based on the lag + of maximum CE for each pair. + + Parameters + ---------- + j : int + Index of variable. + + lag_mode : {'absmax', 'all_lags'} + Lag mode. Either average across all lags between each pair or only + at the lag of maximum absolute causal effect. + + exclude_j : bool, optional (default: True) + Whether to exclude causal effects on the variable itself at previous + lags. + + Returns + ------- + acs : float + Average Causal Susceptibility. + """ + + all_but_j = np.ones(self.N, dtype='bool') + if exclude_j: + all_but_j[j] = False + + if lag_mode == 'absmax': + return np.abs(self.psi[1:, j, all_but_j]).max(axis=0).mean() + elif lag_mode == 'all_lags': + return np.abs(self.psi[1:, j, all_but_j]).mean() + else: + raise ValueError("lag_mode = %s not implemented" % lag_mode)
+ +
[docs] def get_all_acs(self, lag_mode='absmax', exclude_j=True): + """Returns the average causal susceptibility. + + This is the Average Causal Susceptibility (ACS) for each variable from + any other variable. With lag_mode='absmax' this is based on the lag of + maximum CE for each pair. + + Parameters + ---------- + lag_mode : {'absmax', 'all_lags'} + Lag mode. Either average across all lags between each pair or only + at the lag of maximum absolute causal effect. + + exclude_j : bool, optional (default: True) + Whether to exclude causal effects on the variable itself at previous + lags. + + Returns + ------- + acs : array of shape (N,) + Average Causal Susceptibility. + """ + + acs = np.zeros(self.N) + for j in range(self.N): + acs[j] = self.get_acs(j, lag_mode=lag_mode, exclude_j=exclude_j) + + return acs
+ +
[docs] def get_amce(self, k, lag_mode='absmax', + exclude_k=True, exclude_self_effects=True): + """Returns the average mediated causal effect. + + This is the Average Mediated Causal Effect (AMCE) through a variable k + With lag_mode='absmax' this is based on the lag of maximum CE for each + pair. + + Parameters + ---------- + k : int + Index of variable. + + lag_mode : {'absmax', 'all_lags'} + Lag mode. Either average across all lags between each pair or only + at the lag of maximum absolute causal effect. + + exclude_k : bool, optional (default: True) + Whether to exclude causal effects through the variable itself at + previous lags. + + exclude_self_effects : bool, optional (default: True) + Whether to exclude causal self effects of variables on themselves. + + Returns + ------- + amce : float + Average Mediated Causal Effect. + """ + + all_but_k = np.ones(self.N, dtype='bool') + if exclude_k: + all_but_k[k] = False + N_new = self.N - 1 + else: + N_new = self.N + + if exclude_self_effects: + weights = np.identity(N_new) == False + else: + weights = np.ones((N_new, N_new), dtype = 'bool') + + if self.tau_max < 2: + raise ValueError("Mediation only nonzero for tau_max >= 2") + + all_mce = self.psi[2:, :, :] - self.all_psi_k[k, 2:, :, :] + # all_mce[:, range(self.N), range(self.N)] = 0. + + if lag_mode == 'absmax': + return np.average(np.abs(all_mce[:, all_but_k, :] + [:, :, all_but_k] + ).max(axis=0), weights=weights) + elif lag_mode == 'all_lags': + return np.abs(all_mce[:, all_but_k, :][:,:, all_but_k]).mean() + else: + raise ValueError("lag_mode = %s not implemented" % lag_mode)
+ + +
[docs] def get_all_amce(self, lag_mode='absmax', + exclude_k=True, exclude_self_effects=True): + """Returns the average mediated causal effect. + + This is the Average Mediated Causal Effect (AMCE) through all variables + With lag_mode='absmax' this is based on the lag of maximum CE for each + pair. + + Parameters + ---------- + lag_mode : {'absmax', 'all_lags'} + Lag mode. Either average across all lags between each pair or only + at the lag of maximum absolute causal effect. + + exclude_k : bool, optional (default: True) + Whether to exclude causal effects through the variable itself at + previous lags. + + exclude_self_effects : bool, optional (default: True) + Whether to exclude causal self effects of variables on themselves. + + Returns + ------- + amce : array of shape (N,) + Average Mediated Causal Effect. + """ + amce = np.zeros(self.N) + for k in range(self.N): + amce[k] = self.get_amce(k, + lag_mode=lag_mode, + exclude_k=exclude_k, + exclude_self_effects=exclude_self_effects) + + return amce
+ + +
[docs]class Prediction(Models, PCMCI): + r"""Prediction class for time series models. + + Allows to fit and predict from any sklearn model. The optimal predictors can + be estimated using PCMCI. Also takes care of missing values, masking and + preprocessing. + + Parameters + ---------- + dataframe : data object + Tigramite dataframe object. It must have the attributes dataframe.values + yielding a numpy array of shape (observations T, variables N) and + optionally a mask of the same shape and a missing values flag. + + train_indices : array-like + Either boolean array or time indices marking the training data. + + test_indices : array-like + Either boolean array or time indices marking the test data. + + prediction_model : sklearn model object + For example, sklearn.linear_model.LinearRegression() for a linear + regression model. + + cond_ind_test : Conditional independence test object, optional + Only needed if predictors are estimated with causal algorithm. + The class will be initialized with masking set to the training data. + + data_transform : sklearn preprocessing object, optional (default: None) + Used to transform data prior to fitting. For example, + sklearn.preprocessing.StandardScaler for simple standardization. The + fitted parameters are stored. + + verbosity : int, optional (default: 0) + Level of verbosity. + """ + def __init__(self, + dataframe, + train_indices, + test_indices, + prediction_model, + cond_ind_test=None, + data_transform=None, + verbosity=0): + + # Default value for the mask + mask = dataframe.mask + if mask is None: + mask = np.zeros(dataframe.values.shape, dtype='bool') + # Get the dataframe shape + T = len(dataframe.values) + # Have the default dataframe be the training data frame + train_mask = np.copy(mask) + train_mask[[t for t in range(T) if t not in train_indices]] = True + self.dataframe = DataFrame(dataframe.values, + mask=train_mask, + missing_flag=dataframe.missing_flag) + # Initialize the models baseclass with the training dataframe + Models.__init__(self, + dataframe=self.dataframe, + model=prediction_model, + data_transform=data_transform, + mask_type='y', + verbosity=verbosity) + + # Build the testing dataframe as well + self.test_mask = np.copy(mask) + self.test_mask[[t for t in range(T) if t not in test_indices]] = True + + # Setup the PCMCI instance + if cond_ind_test is not None: + # Force the masking + cond_ind_test.set_mask_type('y') + cond_ind_test.verbosity = verbosity + PCMCI.__init__(self, + dataframe=self.dataframe, + cond_ind_test=cond_ind_test, + selected_variables=None, + verbosity=verbosity) + + # Set the member variables + self.cond_ind_test = cond_ind_test + # Initialize member varialbes that are set outside + self.target_predictors = None + self.selected_targets = None + self.fitted_model = None + self.test_array = None + +
[docs] def get_predictors(self, + selected_targets=None, + selected_links=None, + steps_ahead=1, + tau_max=1, + pc_alpha=0.2, + max_conds_dim=None, + max_combinations=1): + """Estimate predictors using PC1 algorithm. + + Wrapper around PCMCI.run_pc_stable that estimates causal predictors. + The lead time can be specified by ``steps_ahead``. + + Parameters + ---------- + selected_targets : list of ints, optional (default: None) + List of variables to estimate predictors of. If None, predictors of + all variables are estimated. + + selected_links : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + + steps_ahead : int, default: 1 + Minimum time lag to test. Useful for multi-step ahead predictions. + + tau_max : int, default: 1 + Maximum time lag. Must be larger or equal to tau_min. + + pc_alpha : float or list of floats, default: 0.2 + Significance level in algorithm. If a list or None is passed, the + pc_alpha level is optimized for every variable across the given + pc_alpha values using the score computed in + cond_ind_test.get_model_selection_criterion() + + max_conds_dim : int or None + Maximum number of conditions to test. If None is passed, this number + is unrestricted. + + max_combinations : int, default: 1 + Maximum number of combinations of conditions of current cardinality + to test. Defaults to 1 for PC_1 algorithm. For original PC algorithm + a larger number, such as 10, can be used. + + Returns + ------- + predictors : dict + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + containing estimated predictors. + """ + # Ensure an independence model is given + if self.cond_ind_test is None: + raise ValueError("No cond_ind_test given!") + # Set the selected variables + self.selected_variables = range(self.N) + if selected_targets is not None: + self.selected_variables = selected_targets + predictors = self.run_pc_stable(selected_links=selected_links, + tau_min=steps_ahead, + tau_max=tau_max, + save_iterations=False, + pc_alpha=pc_alpha, + max_conds_dim=max_conds_dim, + max_combinations=max_combinations) + return predictors
+ +
[docs] def fit(self, target_predictors, + selected_targets=None, tau_max=None, return_data=False): + r"""Fit time series model. + + Wrapper around ``Models.get_fit()``. To each variable in + ``selected_targets``, the sklearn model is fitted with :math:`y` given + by the target variable, and :math:`X` given by its predictors. The + fitted model class is returned for later use. + + Parameters + ---------- + target_predictors : dictionary + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} containing + the predictors estimated with PCMCI. + + selected_targets : list of integers, optional (default: range(N)) + Specify to fit model only for selected targets. If None is + passed, models are estimated for all variables. + + tau_max : int, optional (default: None) + Maximum time lag. If None, the maximum lag in target_predictors is + used. + + return_data : bool, optional (default: False) + Whether to save the data array. + + Returns + ------- + self : instance of self + """ + + self.target_predictors = target_predictors + + if selected_targets is None: + self.selected_targets = range(self.N) + else: + self.selected_targets = selected_targets + + for target in self.selected_targets: + if target not in list(self.target_predictors): + raise ValueError("No predictors given for target %s" % target) + + self.fitted_model = \ + self.get_fit(all_parents=self.target_predictors, + selected_variables=self.selected_targets, + tau_max=tau_max, + return_data=return_data) + return self
+ +
[docs] def predict(self, target, + new_data=None, + pred_params=None, + cut_off='max_lag_or_tau_max'): + r"""Predict target variable with fitted model. + + Uses the model.predict() function of the sklearn model. + + Parameters + ---------- + target : int + Index of target variable. + + new_data : data object, optional + New Tigramite dataframe object with optional new mask. + + pred_params : dict, optional + Optional parameters passed on to sklearn prediction function. + + cut_off : {'2xtau_max', 'max_lag', 'max_lag_or_tau_max'} + How many samples to cutoff at the beginning. The default is + '2xtau_max', which guarantees that MCI tests are all conducted on + the same samples. For modeling, 'max_lag_or_tau_max' can be used, + which uses the maximum of tau_max and the conditions, which is + useful to compare multiple models on the same sample. Last, + 'max_lag' uses as much samples as possible. + + Returns + ------- + Results from prediction. + """ + # Print message + if self.verbosity > 0: + print("\n##\n## Predicting target %s\n##" % target) + if pred_params is not None: + for key in list(pred_params): + print("%s = %s" % (key, pred_params[key])) + # Default value for pred_params + if pred_params is None: + pred_params = {} + # Check this is a valid target + if target not in self.selected_targets: + raise ValueError("Target %s not yet fitted" % target) + # Construct the array form of the data + Y = [(target, 0)] + X = [(target, 0)] # dummy + Z = self.target_predictors[target] + # Check if we've passed a new dataframe object + test_array = None + if new_data is not None: + test_array, _ = new_data.construct_array(X, Y, Z, + tau_max=self.tau_max, + mask_type=self.mask_type, + cut_off=cut_off, + verbosity=self.verbosity) + # Otherwise use the default values + else: + test_array, _ = \ + self.dataframe.construct_array(X, Y, Z, + tau_max=self.tau_max, + mask=self.test_mask, + mask_type=self.mask_type, + cut_off=cut_off, + verbosity=self.verbosity) + # Transform the data if needed + a_transform = self.fitted_model[target]['data_transform'] + if a_transform is not None: + test_array = a_transform.transform(X=test_array.T).T + # Cache the test array + self.test_array = test_array + # Run the predictor + pred = self.fitted_model[target]['model'].predict(X=test_array[2:].T, + **pred_params) + return pred
+ +
[docs] def get_train_array(self, j): + """Returns training array.""" + return self.fitted_model[j]['data']
+ +
[docs] def get_test_array(self): + """Returns test array.""" + return self.test_array
+
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/tigramite/pcmci.html b/docs/_build/html/_modules/tigramite/pcmci.html new file mode 100644 index 00000000..c4da6814 --- /dev/null +++ b/docs/_build/html/_modules/tigramite/pcmci.html @@ -0,0 +1,1882 @@ + + + + + + + tigramite.pcmci — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

Source code for tigramite.pcmci

+"""Tigramite causal discovery for time series."""
+
+# Author: Jakob Runge <jakob@jakob-runge.com>
+#
+# License: GNU General Public License v3.0
+
+from __future__ import print_function
+import itertools
+from collections import defaultdict
+from copy import deepcopy
+import numpy as np
+
+def _create_nested_dictionary(depth=0, lowest_type=dict):
+    """Create a series of nested dictionaries to a maximum depth.  The first
+    depth - 1 nested dictionaries are defaultdicts, the last is a normal
+    dictionary.
+
+    Parameters
+    ----------
+    depth : int
+        Maximum depth argument.
+    lowest_type: callable (optional)
+        Type contained in leaves of tree.  Ex: list, dict, tuple, int, float ...
+    """
+    new_depth = depth - 1
+    if new_depth <= 0:
+        return defaultdict(lowest_type)
+    return defaultdict(lambda: _create_nested_dictionary(new_depth))
+
+def _nested_to_normal(nested_dict):
+    """Transforms the nested default dictionary into a standard dictionaries
+
+    Parameters
+    ----------
+    nested_dict : default dictionary of default dictionaries of ... etc.
+    """
+    if isinstance(nested_dict, defaultdict):
+        nested_dict = {k: _nested_to_normal(v) for k, v in nested_dict.items()}
+    return nested_dict
+
+
[docs]class PCMCI(): + r"""PCMCI causal discovery for time series datasets. + + PCMCI is a 2-step causal discovery method for large-scale time series + datasets. The first step is a condition-selection followed by the MCI + conditional independence test. The implementation is based on Algorithms 1 + and 2 in [1]_. + + PCMCI allows: + + * different conditional independence test statistics adapted to + continuously-valued or discrete data, and different assumptions about + linear or nonlinear dependencies + * hyperparameter optimization + * easy parallelization + * handling of masked time series data + * false discovery control and confidence interval estimation + + + Notes + ----- + + .. image:: mci_schematic.* + :width: 200pt + + The PCMCI causal discovery method is comprehensively described in [1]_, + where also analytical and numerical results are presented. Here we briefly + summarize the method. + + In the PCMCI framework, the dependency structure of a set of + time series variables is represented in a *time series graph* as shown in + the Figure. The nodes of a time series graph are defined as the variables at + different times and a link exists if two lagged variables are *not* + conditionally independent given the past of the whole process. Assuming + stationarity, the links are repeated in time. The parents + :math:`\mathcal{P}` of a variable are defined as the set of all nodes with a + link towards it (blue and red boxes in Figure). Estimating these parents + directly by testing for conditional independence on the whole past is + problematic due to high-dimensionality and because conditioning on + irrelevant variables leads to biases [1]_. + + PCMCI estimates causal links by a two-step procedure: + + 1. Condition-selection: For each variable :math:`j`, estimate a + *superset* of parents :math:`\tilde{\mathcal{P}}(X^j_t)` with the + iterative PC1 algorithm , implemented as ``run_pc_stable``. + + 2. *Momentary conditional independence* (MCI) + + .. math:: X^i_{t-\tau} ~\perp~ X^j_{t} ~|~ \tilde{\mathcal{P}}(X^j_t), + \tilde{\mathcal{P}}(X^i_{t-{\tau}}) + + here implemented as ``run_mci``. The condition-selection step reduces the + dimensionality and avoids conditioning on irrelevant variables. + + PCMCI can be flexibly combined with any kind of conditional independence + test statistic adapted to the kind of data (continuous or discrete) and its + assumed dependency structure. Currently, implemented in Tigramite are + ParCorr as a linear test, GPACE allowing nonlinear additive dependencies, + and CMI with different estimators making no assumptions about the + dependencies. The classes in ``tigramite.independence_tests`` also handle + masked data. + + The main free parameters of PCMCI (in addition to free parameters of the + conditional independence test statistic) are the maximum time delay + :math:`\tau_{\max}` (``tau_max``) and the significance threshold in the + condition- selection step :math:`\alpha` (``pc_alpha``). The maximum time + delay depends on the application and should be chosen according to the + maximum causal time lag expected in the complex system. We recommend a + rather large choice that includes peaks in the lagged cross-correlation + function (or a more general measure). :math:`\alpha` should not be seen as a + significance test level in the condition-selection step since the iterative + hypothesis tests do not allow for a precise confidence level. :math:`\alpha` + rather takes the role of a regularization parameter in model-selection + techniques. The conditioning sets :math:`\tilde{\mathcal{P}}` should + include the true parents and at the same time be small in size to reduce the + estimation dimension of the MCI test and improve its power. But including + the true parents is typically more important. If a list of values is given + or ``pc_alpha=None``, :math:`\alpha` is optimized using model selection + criteria. + + Further optional parameters are discussed in [1]_. + + References + ---------- + + .. [1] J. Runge, P. Nowack, M. Kretschmer, S. Flaxman, D. Sejdinovic, + Detecting and quantifying causal associations in large nonlinear time + series datasets. Sci. Adv. 5, eaau4996 (2019) + https://advances.sciencemag.org/content/5/11/eaau4996 + + Examples + -------- + >>> import numpy + >>> from tigramite.pcmci import PCMCI + >>> from tigramite.independence_tests import ParCorr + >>> import tigramite.data_processing as pp + >>> numpy.random.seed(42) + >>> # Example process to play around with + >>> # Each key refers to a variable and the incoming links are supplied as a + >>> # list of format [((driver, lag), coeff), ...] + >>> links_coeffs = {0: [((0, -1), 0.8)], + 1: [((1, -1), 0.8), ((0, -1), 0.5)], + 2: [((2, -1), 0.8), ((1, -2), -0.6)]} + >>> data, _ = pp.var_process(links_coeffs, T=1000) + >>> # Data must be array of shape (time, variables) + >>> print data.shape + (1000, 3) + >>> dataframe = pp.DataFrame(data) + >>> cond_ind_test = ParCorr() + >>> pcmci = PCMCI(dataframe=dataframe, cond_ind_test=cond_ind_test) + >>> results = pcmci.run_pcmci(tau_max=2, pc_alpha=None) + >>> pcmci._print_significant_links(p_matrix=results['p_matrix'], + val_matrix=results['val_matrix'], + alpha_level=0.05) + ## Significant parents at alpha = 0.05: + Variable 0 has 1 parent(s): + (0 -1): pval = 0.00000 | val = 0.623 + Variable 1 has 2 parent(s): + (1 -1): pval = 0.00000 | val = 0.601 + (0 -1): pval = 0.00000 | val = 0.487 + Variable 2 has 2 parent(s): + (2 -1): pval = 0.00000 | val = 0.597 + (1 -2): pval = 0.00000 | val = -0.511 + + Parameters + ---------- + dataframe : data object + This is the Tigramite dataframe object. It has the attributes + dataframe.values yielding a numpy array of shape (observations T, + variables N) and optionally a mask of the same shape. + + cond_ind_test : conditional independence test object + This can be ParCorr or other classes from the tigramite package or an + external test passed as a callable. This test can be based on the class + tigramite.independence_tests.CondIndTest. If a callable is passed, it + must have the signature:: + + class CondIndTest(): + # with attributes + # * measure : str + # name of the test + # * use_mask : bool + # whether the mask should be used + + # and functions + # * run_test(X, Y, Z, tau_max) : where X,Y,Z are of the form + # X = [(var, -tau)] for non-negative integers var and tau + # specifying the variable and time lag + # return (test statistic value, p-value) + # * set_dataframe(dataframe) : set dataframe object + + # optionally also + + # * get_model_selection_criterion(j, parents) : required if + # pc_alpha parameter is to be optimized. Here j is the + # variable index and parents a list [(var, -tau), ...] + # return score for model selection + # * get_confidence(X, Y, Z, tau_max) : required for + # return_confidence=True + # estimate confidence interval after run_test was called + # return (lower bound, upper bound) + + selected_variables : list of integers, optional (default: range(N)) + Specify to estimate parents only for selected variables. If None is + passed, parents are estimated for all variables. Note that parents + can still come from all variables, you can restrict parents using + the selected_links parameter. + + verbosity : int, optional (default: 0) + Verbose levels 0, 1, ... + + Attributes + ---------- + all_parents : dictionary + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} containing the + conditioning-parents estimated with PC algorithm. + + val_min : dictionary + Dictionary of form val_min[j][(i, -tau)] = float + containing the minimum test statistic value for each link estimated in + the PC algorithm. + + p_max : dictionary + Dictionary of form p_max[j][(i, -tau)] = float containing the maximum + p-value for each link estimated in the PC algorithm. + + iterations : dictionary + Dictionary containing further information on algorithm steps. + + N : int + Number of variables. + + T : int + Time series sample length. + + """ + def __init__(self, dataframe, + cond_ind_test, + selected_variables=None, + verbosity=0): + # Set the data for this iteration of the algorithm + self.dataframe = dataframe + # Set the conditional independence test to be used + self.cond_ind_test = cond_ind_test + self.cond_ind_test.set_dataframe(self.dataframe) + # Set the verbosity for debugging/logging messages + self.verbosity = verbosity + # Set the variable names + self.var_names = self.dataframe.var_names + + # Store the shape of the data in the T and N variables + self.T, self.N = self.dataframe.values.shape + # Set the selected variables + self.selected_variables = \ + self._set_selected_variables(selected_variables) + + def _set_selected_variables(self, selected_variables): + """Helper function to set and check the selected variables argument + + Parameters + ---------- + selected_variables : list or None + List of variable ID's from the input data set + + Returns + ------- + selected_variables : list + Defaults to a list of all given variable IDs [0..N-1] + """ + # Set the default selected variables if none are set + _int_selected_variables = deepcopy(selected_variables) + if _int_selected_variables is None: + _int_selected_variables = range(self.N) + # Some checks + if _int_selected_variables is not None and \ + (np.any(np.array(_int_selected_variables) < 0) or + np.any(np.array(_int_selected_variables) >= self.N)): + raise ValueError("selected_variables must be within 0..N-1") + # Ensure there are only unique values + _int_selected_variables = sorted(list(set(_int_selected_variables))) + # Return the selected variables + return _int_selected_variables + + def _set_sel_links(self, selected_links, tau_min, tau_max): + """Helper function to set and check the selected links argument + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are returned + tau_mix : int + Minimum time delay to test + tau_max : int + Maximum time delay to test + + Returns + ------- + selected_variables : list + Defaults to a list of all given variable IDs [0..N-1] + """ + # Copy and pass into the function + _int_sel_links = deepcopy(selected_links) + # Set the default selected links if none are set + _vars = list(range(self.N)) + if _int_sel_links is None: + _int_sel_links = {} + # Set the default as all combinations of the selected variables + for j in _vars: + # If it is in selected variables, select all possible links + if j in self.selected_variables: + _int_sel_links[j] = [(var, -lag) for var in _vars + for lag in range(tau_min, tau_max + 1)] + # If it is not, make it an empty list + else: + _int_sel_links[j] = [] + # Otherwise, check that our selection is sane + # Check that the selected links refer to links that are inside the + # data range + _key_set = set(_int_sel_links.keys()) + valid_entries = _key_set.issubset(_vars) + valid_entries = valid_entries and \ + set(var for parents in _int_sel_links.values() + for var, _ in parents).issubset(_vars) + if not valid_entries: + raise ValueError("Out of range variable defined in \n", + _int_sel_links, + "\nMust be in range [0, ", self.N-1, "]") + + ## Note: variables are scoped by selected_variables first, and then + ## by selected links. Add to docstring? + # Return the selected links + return _int_sel_links + + def _iter_conditions(self, parent, conds_dim, all_parents): + """Yield next condition. + + Yields next condition from lexicographically ordered conditions. + + Parameters + ---------- + parent : tuple + Tuple of form (i, -tau). + conds_dim : int + Cardinality in current step. + all_parents : list + List of form [(0, -1), (3, -2), ...] + + Yields + ------- + cond : list + List of form [(0, -1), (3, -2), ...] for the next condition. + """ + all_parents_excl_current = [p for p in all_parents if p != parent] + for cond in itertools.combinations(all_parents_excl_current, conds_dim): + yield list(cond) + + def _sort_parents(self, parents_vals): + """Sort current parents according to test statistic values. + + Sorting is from strongest to weakest absolute values. + + Parameters + --------- + parents_vals : dict + Dictionary of form {(0, -1):float, ...} containing the minimum test + statistic value of a link + + Returns + ------- + parents : list + List of form [(0, -1), (3, -2), ...] containing sorted parents. + """ + if self.verbosity > 1: + print("\n Sorting parents in decreasing order with " + "\n weight(i-tau->j) = min_{iterations} |I_{ij}(tau)| ") + # Get the absoute value for all the test statistics + abs_values = {k : np.abs(parents_vals[k]) for k in list(parents_vals)} + return sorted(abs_values, key=abs_values.get, reverse=True) + + def _dict_to_matrix(self, val_dict, tau_max, n_vars): + """Helper function to convert dictionary to matrix formart. + + Parameters + --------- + val_dict : dict + Dictionary of form {0:{(0, -1):float, ...}, 1:{...}, ...} + tau_max : int + Maximum lag. + + Returns + ------- + matrix : array of shape (N, N, tau_max+1) + Matrix format of p-values and test statistic values. + """ + matrix = np.ones((n_vars, n_vars, tau_max + 1)) + for j in val_dict.keys(): + for link in val_dict[j].keys(): + k, tau = link + matrix[k, j, abs(tau)] = val_dict[j][link] + return matrix + + def _print_link_info(self, j, index_parent, parent, num_parents): + """Print info about the current link being tested + + Parameters + ---------- + j : int + Index of current node being tested + index_parent : int + Index of the current parent + parent : tuple + Standard (i, tau) tuple of parent node id and time delay + num_parents : int + Total number of parents + """ + print("\n Link (%s %d) --> %s (%d/%d):" % ( + self.var_names[parent[0]], parent[1], self.var_names[j], + index_parent + 1, num_parents)) + + def _print_cond_info(self, Z, comb_index, pval, val): + """Print info about the condition + + Parameters + ---------- + Z : list + The current condition being tested + comb_index : int + Index of the combination yielding this condition + pval : float + p-value from this condition + val : float + value from this condition + """ + var_name_z = "" + for i, tau in Z: + var_name_z += "(%s %d) " % (self.var_names[i], tau) + print(" Combination %d: %s --> pval = %.5f / val = %.3f" % + (comb_index, var_name_z, pval, val)) + + def _print_a_pc_result(self, pval, pc_alpha, conds_dim, max_combinations): + """ + Print the results from the current iteration of conditions. + + Parameters + ---------- + pval : float + pval to check signficance + pc_alpha : float + lower bound on what is considered significant + conds_dim : int + Cardinality of the current step + max_combinations : int + Maximum number of combinations of conditions of current cardinality + to test. + """ + # Start with an indent + print_str = " " + # Determine the body of the text + if pval > pc_alpha: + print_str += "Non-significance detected." + elif conds_dim > max_combinations: + print_str += "Still conditions of dimension"+\ + " %d left," % (conds_dim) +\ + " but q_max = %d reached." % (max_combinations) + else: + print_str += "No conditions of dimension %d left." % (conds_dim) + # Print the message + print(print_str) + + def _print_converged_pc_single(self, converged, j, max_conds_dim): + """ + Print statement about the convergence of the pc_stable_single algorithm. + + Parameters + ---------- + convergence : bool + true if convergence was reacjed + j : int + Variable index. + max_conds_dim : int + Maximum number of conditions to test + """ + if converged: + print("\nAlgorithm converged for variable %s" % + self.var_names[j]) + else: + print( + "\nAlgorithm not yet converged, but max_conds_dim = %d" + " reached." % max_conds_dim) + + def _run_pc_stable_single(self, j, + selected_links=None, + tau_min=1, + tau_max=1, + save_iterations=False, + pc_alpha=0.2, + max_conds_dim=None, + max_combinations=1): + """PC algorithm for estimating parents of single variable. + + Parameters + ---------- + j : int + Variable index. + + selected_links : list, optional (default: None) + List of form [(0, -1), (3, -2), ...] + specifying whether only selected links should be tested. If None is + passed, all links are tested + + tau_min : int, optional (default: 1) + Minimum time lag to test. Useful for variable selection in + multi-step ahead predictions. Must be greater zero. + + tau_max : int, optional (default: 1) + Maximum time lag. Must be larger or equal to tau_min. + + save_iterations : bool, optional (default: False) + Whether to save iteration step results such as conditions used. + + pc_alpha : float or None, optional (default: 0.2) + Significance level in algorithm. If a list is given, pc_alpha is + optimized using model selection criteria provided in the + cond_ind_test class as get_model_selection_criterion(). If None, + a default list of values is used. + + max_conds_dim : int, optional (default: None) + Maximum number of conditions to test. If None is passed, this number + is unrestricted. + + max_combinations : int, optional (default: 1) + Maximum number of combinations of conditions of current cardinality + to test. Defaults to 1 for PC_1 algorithm. For original PC algorithm + a larger number, such as 10, can be used. + + Returns + ------- + parents : list + List of estimated parents. + + val_min : dict + Dictionary of form {(0, -1):float, ...} containing the minimum test + statistic value of a link. + + p_max : dict + Dictionary of form {(0, -1):float, ...} containing the maximum + p-value of a link across different conditions. + + iterations : dict + Dictionary containing further information on algorithm steps. + """ + # Initialize the dictionaries for the p_max, val_min parents_values + # results + p_max = dict() + val_min = dict() + parents_values = dict() + # Initialize the parents values from the selected links, copying to + # ensure this initial argument is unchagned. + parents = deepcopy(selected_links) + # Define a nested defaultdict of depth 4 to save all information about + # iterations + iterations = _create_nested_dictionary(4) + # Ensure tau_min is atleast 1 + tau_min = max(1, tau_min) + + # Loop over all possible condition dimentions + max_conds_dim = self._set_max_condition_dim(max_conds_dim, + tau_min, tau_max) + # Iteration through increasing number of conditions, i.e. from + # [0,max_conds_dim] inclusive + converged = False + for conds_dim in range(max_conds_dim+1): + # (Re)initialize the list of non-significant links + nonsig_parents = list() + # Check if the algorithm has converged + if len(parents) - 1 < conds_dim: + converged = True + break + # Print information about + if self.verbosity > 1: + print("\nTesting condition sets of dimension %d:" % conds_dim) + + # Iterate through all possible pairs (that have not converged yet) + for index_parent, parent in enumerate(parents): + # Print info about this link + if self.verbosity > 1: + self._print_link_info(j, index_parent, parent, len(parents)) + # Iterate through all possible combinations + for comb_index, Z in \ + enumerate(self._iter_conditions(parent, conds_dim, + parents)): + # Break if we try too many combinations + if comb_index >= max_combinations: + break + # Perform independence test + val, pval = self.cond_ind_test.run_test(X=[parent], + Y=[(j, 0)], + Z=Z, + tau_max=tau_max) + # Print some information if needed + if self.verbosity > 1: + self._print_cond_info(Z, comb_index, pval, val) + # Keep track of maximum p-value and minimum estimated value + # for each pair (across any condition) + parents_values[parent] = \ + min(np.abs(val), parents_values.get(parent, + float("inf"))) + p_max[parent] = \ + max(np.abs(pval), p_max.get(parent, -float("inf"))) + val_min[parent] = \ + min(np.abs(val), val_min.get(parent, float("inf"))) + # Save the iteration if we need to + if save_iterations: + a_iter = iterations['iterations'][conds_dim][parent] + a_iter[comb_index]['conds'] = deepcopy(Z) + a_iter[comb_index]['val'] = val + a_iter[comb_index]['pval'] = pval + # Delete link later and break while-loop if non-significant + if pval > pc_alpha: + nonsig_parents.append((j, parent)) + break + + # Print the results if needed + if self.verbosity > 1: + self._print_a_pc_result(pval, pc_alpha, + conds_dim, max_combinations) + + # Remove non-significant links + for _, parent in nonsig_parents: + del parents_values[parent] + # Return the parents list sorted by the test metric so that the + # updated parents list is given to the next cond_dim loop + parents = self._sort_parents(parents_values) + # Print information about the change in possible parents + if self.verbosity > 1: + print("\nUpdating parents:") + self._print_parents_single(j, parents, parents_values, p_max) + + # Print information about if convergence was reached + if self.verbosity > 1: + self._print_converged_pc_single(converged, j, max_conds_dim) + # Return the results + return {'parents':parents, + 'val_min':val_min, + 'p_max':p_max, + 'iterations': _nested_to_normal(iterations)} + + def _print_pc_params(self, selected_links, tau_min, tau_max, pc_alpha, + max_conds_dim, max_combinations): + """ + Print the setup of the current pc_stable run + + Parameters + ---------- + selected_links : dict or None + Dictionary of form specifying which links should be tested. + tau_min : int, default: 1 + Minimum time lag to test. + tau_max : int, default: 1 + Maximum time lag to test + pc_alpha : float or list of floats + Significance level in algorithm. + max_conds_dim : int + Maximum number of conditions to test. + max_combinations : int + Maximum number of combinations of conditions to test. + """ + print("\n##\n## Running Tigramite PC algorithm\n##" + "\n\nParameters:") + if len(self.selected_variables) < self.N: + print("selected_variables = %s" % self.selected_variables) + if selected_links is not None: + print("selected_links = %s" % selected_links) + print("independence test = %s" % self.cond_ind_test.measure + + "\ntau_min = %d" % tau_min + + "\ntau_max = %d" % tau_max + + "\npc_alpha = %s" % pc_alpha + + "\nmax_conds_dim = %s" % max_conds_dim + + "\nmax_combinations = %d" % max_combinations) + print("\n") + + def _print_pc_sel_results(self, pc_alpha, results, j, score, optimal_alpha): + """ + Print the results from the pc_alpha selection + + Parameters + ---------- + pc_alpha : list + Tested significance levels in algorithm. + results : dict + Results from the tested pc_alphas + score : array of floats + scores from each pc_alpha + j : int + Index of current variable. + optimal_alpha : float + Optimal value of pc_alpha + """ + print("\n# Condition selection results:") + for iscore, pc_alpha_here in enumerate(pc_alpha): + names_parents = "[ " + for pari in results[pc_alpha_here]['parents']: + names_parents += "(%s %d) " % ( + self.var_names[pari[0]], pari[1]) + names_parents += "]" + print(" pc_alpha=%s got score %.4f with parents %s" % + (pc_alpha_here, score[iscore], names_parents)) + print("\n--> optimal pc_alpha for variable %s is %s" % + (self.var_names[j], optimal_alpha)) + + def _check_tau_limits(self, tau_min, tau_max): + """ + Check the tau limits adhere to 0 <= tau_min <= tau_max + + Parameters + ---------- + tau_min : float + Minimum tau value. + tau_max : float + Maximum tau value. + """ + if not 0 <= tau_min <= tau_max: + raise ValueError("tau_max = %d, " % (tau_max) +\ + "tau_min = %d, " % (tau_min) +\ + "but 0 <= tau_min <= tau_max") + + def _set_max_condition_dim(self, max_conds_dim, tau_min, tau_max): + """ + Set the maximum dimension of the conditions. Defaults to self.N*tau_max + + Parameters + ---------- + max_conds_dim : int + Input maximum condition dimension + tau_max : int + Maximum tau. + + Returns + ------- + max_conds_dim : int + Input maximum condition dimension or default + """ + # Check if an input was given + if max_conds_dim is None: + max_conds_dim = self.N * (tau_max - tau_min + 1) + # Check this is a valid + if max_conds_dim < 0: + raise ValueError("maximum condition dimension must be >= 0") + return max_conds_dim + +
[docs] def run_pc_stable(self, + selected_links=None, + tau_min=1, + tau_max=1, + save_iterations=False, + pc_alpha=0.2, + max_conds_dim=None, + max_combinations=1): + """PC algorithm for estimating parents of all variables. + + Parents are made available as self.all_parents + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + + tau_min : int, default: 1 + Minimum time lag to test. Useful for multi-step ahead predictions. + Must be greater zero. + + tau_max : int, default: 1 + Maximum time lag. Must be larger or equal to tau_min. + + save_iterations : bool, default: False + Whether to save iteration step results such as conditions used. + + pc_alpha : float or list of floats, default: [0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + Significance level in algorithm. If a list or None is passed, the + pc_alpha level is optimized for every variable across the given + pc_alpha values using the score computed in + cond_ind_test.get_model_selection_criterion() + + max_conds_dim : int or None + Maximum number of conditions to test. If None is passed, this number + is unrestricted. + + max_combinations : int, default: 1 + Maximum number of combinations of conditions of current cardinality + to test. Defaults to 1 for PC_1 algorithm. For original PC algorithm + a larger number, such as 10, can be used. + + Returns + ------- + all_parents : dict + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + containing estimated parents. + """ + # Create an internal copy of pc_alpha + _int_pc_alpha = deepcopy(pc_alpha) + # Check if we are selecting an optimal alpha value + select_optimal_alpha = True + # Set the default values for pc_alpha + if _int_pc_alpha is None: + _int_pc_alpha = [0.05, 0.1, 0.2, 0.3, 0.4, 0.5] + elif not isinstance(_int_pc_alpha, (list, tuple, np.ndarray)): + _int_pc_alpha = [_int_pc_alpha] + select_optimal_alpha = False + # Check the limits on tau_min + self._check_tau_limits(tau_min, tau_max) + tau_min = max(1, tau_min) + # Check that the maximum combinatiosn variable is correct + if max_combinations <= 0: + raise ValueError("max_combinations must be > 0") + # Implement defaultdict for all p_max, val_max, and iterations + p_max = defaultdict(dict) + val_min = defaultdict(dict) + iterations = defaultdict(dict) + # Print information about the selected parameters + if self.verbosity > 0: + print("\n##\n## Running Tigramite PC algorithm\n##" + "\n\nParameters:") + if len(self.selected_variables) < self.N: + print("selected_variables = %s" % self.selected_variables) + if selected_links is not None: + print("selected_links = %s" % selected_links) + print("independence test = %s" % self.cond_ind_test.measure + + "\ntau_min = %d" % tau_min + + "\ntau_max = %d" % tau_max + + "\npc_alpha = %s" % pc_alpha + + "\nmax_conds_dim = %s" % max_conds_dim + + "\nmax_combinations = %d" % max_combinations) + print("\n") + + if selected_links is None: + selected_links = {} + for j in range(self.N): + if j in self.selected_variables: + selected_links[j] = [(var, -lag) + for var in range(self.N) + for lag in range(tau_min, tau_max + 1) + ] + else: + selected_links[j] = [] + + if max_conds_dim is None: + max_conds_dim = self.N * tau_max + + if max_conds_dim < 0: + raise ValueError("max_conds_dim must be >= 0") + + self._print_pc_params(selected_links, tau_min, tau_max, + _int_pc_alpha, max_conds_dim, + max_combinations) + # Set the selected links + _int_sel_links = self._set_sel_links(selected_links, tau_min, tau_max) + # Initialize all parents + all_parents = dict() + # Set the maximum condition dimension + max_conds_dim = self._set_max_condition_dim(max_conds_dim, + tau_min, tau_max) + + # Loop through the selected variables + for j in self.selected_variables: + # Print the status of this variable + if self.verbosity > 0: + print("\n## Variable %s" % self.var_names[j]) + if self.verbosity > 1: + print("\nIterating through pc_alpha = %s:" % _int_pc_alpha) + # Initialize the scores for selecting the optimal alpha + score = np.zeros_like(_int_pc_alpha) + # Initialize the result + results = {} + for iscore, pc_alpha_here in enumerate(_int_pc_alpha): + # Print statement about the pc_alpha being tested + if self.verbosity > 1: + print("\n# pc_alpha = %s (%d/%d):" % (pc_alpha_here, + iscore+1, + score.shape[0])) + # Get the results for this alpha value + results[pc_alpha_here] = \ + self._run_pc_stable_single(j, + selected_links=_int_sel_links[j], + tau_min=tau_min, + tau_max=tau_max, + save_iterations=save_iterations, + pc_alpha=pc_alpha_here, + max_conds_dim=max_conds_dim, + max_combinations=max_combinations) + # Figure out the best score if there is more than one pc_alpha + # value + if select_optimal_alpha: + score[iscore] = \ + self.cond_ind_test.get_model_selection_criterion( + j, results[pc_alpha_here]['parents'], tau_max) + # Record the optimal alpha value + optimal_alpha = _int_pc_alpha[score.argmin()] + # Only print the selection results if there is more than one + # pc_alpha + if self.verbosity > 1 and select_optimal_alpha: + self._print_pc_sel_results(_int_pc_alpha, results, j, + score, optimal_alpha) + # Record the results for this variable + all_parents[j] = results[optimal_alpha]['parents'] + val_min[j] = results[optimal_alpha]['val_min'] + p_max[j] = results[optimal_alpha]['p_max'] + iterations[j] = results[optimal_alpha]['iterations'] + # Only save the optimal alpha if there is more than one pc_alpha + if select_optimal_alpha: + iterations[j]['optimal_pc_alpha'] = optimal_alpha + # Save the results in the current status of the algorithm + self.all_parents = all_parents + self.val_matrix = self._dict_to_matrix(val_min, tau_max, self.N) + self.p_matrix = self._dict_to_matrix(p_max, tau_max, self.N) + self.iterations = iterations + self.val_min = val_min + self.p_max = p_max + # Print the results + if self.verbosity > 0: + print("\n## Resulting condition sets:") + self._print_parents(all_parents, val_min, p_max) + # Return the parents + return all_parents
+ + def _print_parents_single(self, j, parents, val_min, p_max): + """Print current parents for variable j. + + Parameters + ---------- + j : int + Index of current variable. + parents : list + List of form [(0, -1), (3, -2), ...] + val_min : dict + Dictionary of form {(0, -1):float, ...} containing the minimum test + statistic value of a link + p_max : dict + Dictionary of form {(0, -1):float, ...} containing the maximum + p-value of a link across different conditions. + """ + if len(parents) < 20 or hasattr(self, 'iterations'): + print("\n Variable %s has %d parent(s):" % ( + self.var_names[j], len(parents))) + if (hasattr(self, 'iterations') + and 'optimal_pc_alpha' in list(self.iterations[j])): + print(" [pc_alpha = %s]" % ( + self.iterations[j]['optimal_pc_alpha'])) + for p in parents: + print(" (%s %d): max_pval = %.5f, min_val = %.3f" % ( + self.var_names[p[0]], p[1], p_max[p], + val_min[p])) + else: + print("\n Variable %s has %d parent(s):" % ( + self.var_names[j], len(parents))) + + def _print_parents(self, all_parents, val_min, p_max): + """Print current parents. + + Parameters + ---------- + all_parents : dictionary + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} containing + the conditioning-parents estimated with PC algorithm. + val_min : dict + Dictionary of form {0:{(0, -1):float, ...}} containing the minimum + test statistic value of a link + p_max : dict + Dictionary of form {0:{(0, -1):float, ...}} containing the maximum + p-value of a link across different conditions. + """ + for j in [var for var in list(all_parents)]: + self._print_parents_single(j, all_parents[j], + val_min[j], p_max[j]) + + def _mci_condition_to_string(self, conds): + """Convert the list of conditions into a string + + Parameters + ---------- + conds : list + List of conditions + """ + cond_string = "[ " + for k, tau_k in conds: + cond_string += "(%s %d) " % (self.var_names[k], tau_k) + cond_string += "]" + return cond_string + + def _print_mci_conditions(self, conds_y, conds_x_lagged, + j, i, tau, count, n_parents): + """Print information about the conditions for the MCI algorithm + + Parameters + ---------- + conds_y : list + Conditions on node + conds_x_lagged : list + Conditions on parent + j : int + Current node + i : int + Parent node + tau : int + Parent time delay + count : int + Index of current parent + n_parents : int + Total number of parents + """ + # Remove the current parent from the conditions + conds_y_no_i = [node for node in conds_y if node != (i, tau)] + # Get the condition string for parent + condy_str = self._mci_condition_to_string(conds_y_no_i) + # Get the condition string for node + condx_str = self._mci_condition_to_string(conds_x_lagged) + # Formate and print the information + indent = "\n " + print_str = indent + "link (%s %d) " % (self.var_names[i], tau) + print_str += "--> %s (%d/%d):" % (self.var_names[j], count+1, n_parents) + print_str += indent + "with conds_y = %s" % (condy_str) + print_str += indent + "with conds_x = %s" % (condx_str) + print(print_str) + + def _get_int_parents(self, parents): + """Get the input parents dictionary + + Parameters + ---------- + parents : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying the conditions for each variable. If None is + passed, no conditions are used. + + Returns + ------- + int_parents : defaultdict of lists + Internal copy of parents, respecting default options + """ + int_parents = deepcopy(parents) + if int_parents is None: + int_parents = defaultdict(list) + else: + int_parents = defaultdict(list, int_parents) + return int_parents + + def _iter_indep_conds(self, + parents, + selected_variables, + selected_links, + max_conds_py, + max_conds_px): + """Iterate through the conditions dictated by the arguments, yielding + the needed arguments for conditional independence functions. + + Parameters + ---------- + parents : dict + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying the conditions for each variable. + selected_variables : list of integers, optional (default: range(N)) + Specify to estimate parents only for selected variables. + selected_links : dict + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. + max_conds_py : int + Maximum number of conditions of Y to use. + max_conds_px : int + Maximum number of conditions of Z to use. + + Yields + ------ + i, j, tau, Z : list of tuples + (i, tau) is the parent node, (j, 0) is the current node, and Z is of + the form [(var, tau + tau')] and specifies the condition to test + """ + # Loop over the selected variables + for j in selected_variables: + # Get the conditions for node j + conds_y = parents[j][:max_conds_py] + # Create a parent list from links seperated in time and by node + parent_list = [(i, tau) for i, tau in selected_links[j] + if (i, tau) != (j, 0)] + # Iterate through parents (except those in conditions) + for cnt, (i, tau) in enumerate(parent_list): + # Get the conditions for node i + conds_x = parents[i][:max_conds_px] + # Shift the conditions for X by tau + conds_x_lagged = [(k, tau + k_tau) for k, k_tau in conds_x] + # Print information about the mci conditions if requested + if self.verbosity > 1: + self._print_mci_conditions(conds_y, conds_x_lagged, j, i, + tau, cnt, len(parent_list)) + # Construct lists of tuples for estimating + # I(X_t-tau; Y_t | Z^Y_t, Z^X_t-tau) + # with conditions for X shifted by tau + Z = [node for node in conds_y if node != (i, tau)] + # Remove overlapped nodes between conds_x_lagged and conds_y + Z += [node for node in conds_x_lagged if node not in Z] + # Yield these list + yield j, i, tau, Z + +
[docs] def get_lagged_dependencies(self, + selected_links=None, + tau_min=0, + tau_max=1, + parents=None, + max_conds_py=None, + max_conds_px=None): + """Returns matrix of lagged dependence measure values. + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + tau_min : int, default: 0 + Minimum time lag. + tau_max : int, default: 1 + Maximum time lag. Must be larger or equal to tau_min. + parents : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying the conditions for each variable. If None is + passed, no conditions are used. + max_conds_py : int or None + Maximum number of conditions from parents of Y to use. If None is + passed, this number is unrestricted. + max_conds_px : int or None + Maximum number of conditions from parents of X to use. If None is + passed, this number is unrestricted. + + Returns + ------- + val_matrix : array + The matrix of shape (N, N, tau_max+1) containing the lagged + dependencies. + """ + # Check the limits on tau + self._check_tau_limits(tau_min, tau_max) + # Set the selected links + _int_sel_links = self._set_sel_links(selected_links, tau_min, tau_max) + # Print status message + if self.verbosity > 0: + print("\n## Estimating lagged dependencies") + # Set the maximum condition dimension for Y and X + max_conds_py = self._set_max_condition_dim(max_conds_py, + tau_min, tau_max) + max_conds_px = self._set_max_condition_dim(max_conds_px, + tau_min, tau_max) + # Get the parents that will be checked + _int_parents = self._get_int_parents(parents) + # Initialize the returned val_matrix + val_matrix = np.zeros((self.N, self.N, tau_max + 1)) + # Get the conditions as implied by the input arguments + for j, i, tau, Z in self._iter_indep_conds(_int_parents, + self.selected_variables, + _int_sel_links, + max_conds_py, + max_conds_px): + # Set X and Y (for clarity of code) + X = [(i, tau)] + Y = [(j, 0)] + # Run the independence test + val = self.cond_ind_test.get_measure(X, Y, Z=Z, tau_max=tau_max) + # Record the value + val_matrix[i, j, abs(tau)] = val + # Print the results + if self.verbosity > 1: + self.cond_ind_test._print_cond_ind_results(val=val) + # Return the value matrix + return val_matrix
+ + def _print_mci_parameters(self, tau_min, tau_max, + max_conds_py, max_conds_px): + """Print the parameters for this MCI algorithm + + Parameters + ---------- + tau_min : int + Minimum time delay + tau_max : int + Maximum time delay + max_conds_py : int + Maximum number of conditions of Y to use. + max_conds_px : int + Maximum number of conditions of Z to use. + """ + print("\n##\n## Running Tigramite MCI algorithm\n##" + "\n\nParameters:") + print("\nindependence test = %s" % self.cond_ind_test.measure + + "\ntau_min = %d" % tau_min + + "\ntau_max = %d" % tau_max + + "\nmax_conds_py = %s" % max_conds_py + + "\nmax_conds_px = %s" % max_conds_px) + +
[docs] def run_mci(self, + selected_links=None, + tau_min=0, + tau_max=1, + parents=None, + max_conds_py=None, + max_conds_px=None): + """MCI conditional independence tests. + + Implements the MCI test (Algorithm 2 in [1]_). Returns the matrices of + test statistic values, p-values, and confidence intervals. + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:all_parents (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + tau_min : int, default: 0 + Minimum time lag to test. Note that zero-lags are undirected. + tau_max : int, default: 1 + Maximum time lag. Must be larger or equal to tau_min. + parents : dict or None + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + specifying the conditions for each variable. If None is + passed, no conditions are used. + max_conds_py : int or None + Maximum number of conditions of Y to use. If None is passed, this + number is unrestricted. + max_conds_px : int or None + Maximum number of conditions of Z to use. If None is passed, this + number is unrestricted. + + Returns + ------- + results : dictionary of arrays of shape [N, N, tau_max+1] + {'val_matrix':val_matrix, 'p_matrix':p_matrix} are always returned + and optionally conf_matrix which is of shape [N, N, tau_max+1,2] + """ + # Check the limits on tau + self._check_tau_limits(tau_min, tau_max) + # Set the selected links + _int_sel_links = self._set_sel_links(selected_links, tau_min, tau_max) + # Print information about the input parameters + if self.verbosity > 0: + self._print_mci_parameters(tau_min, tau_max, + max_conds_py, max_conds_px) + + # Set the maximum condition dimension for Y and X + max_conds_py = self._set_max_condition_dim(max_conds_py, + tau_min, tau_max) + max_conds_px = self._set_max_condition_dim(max_conds_px, + tau_min, tau_max) + # Get the parents that will be checked + _int_parents = self._get_int_parents(parents) + # Initialize the return values + val_matrix = np.zeros((self.N, self.N, tau_max + 1)) + p_matrix = np.ones((self.N, self.N, tau_max + 1)) + # Initialize the optional return of the confidance matrix + conf_matrix = None + if self.cond_ind_test.confidence is not False: + conf_matrix = np.zeros((self.N, self.N, tau_max + 1, 2)) + + # Get the conditions as implied by the input arguments + for j, i, tau, Z in self._iter_indep_conds(_int_parents, + self.selected_variables, + _int_sel_links, + max_conds_py, + max_conds_px): + # Set X and Y (for clarity of code) + X = [(i, tau)] + Y = [(j, 0)] + # Run the independence tests and record the results + val, pval = self.cond_ind_test.run_test(X, Y, Z=Z, tau_max=tau_max) + val_matrix[i, j, abs(tau)] = val + p_matrix[i, j, abs(tau)] = pval + # Get the confidance value, returns None if cond_ind_test.confidance + # is False + conf = self.cond_ind_test.get_confidence(X, Y, Z=Z, tau_max=tau_max) + # Record the value if the conditional independence requires it + if self.cond_ind_test.confidence: + conf_matrix[i, j, abs(tau)] = conf + # Print the results if needed + if self.verbosity > 1: + self.cond_ind_test._print_cond_ind_results(val, + pval=pval, + conf=conf) + # Return the values as a dictionary + return {'val_matrix':val_matrix, + 'p_matrix':p_matrix, + 'conf_matrix':conf_matrix}
+ + def _print_fullci_parameters(self, tau_min, tau_max): + """Print the parameters for this FullCI algorithm + + Parameters + ---------- + tau_min : int + Minimum time delay + tau_max : int + Maximum time delay + """ + print("\n##\n## Running Tigramite FullCI algorithm\n##" + "\n\nParameters:") + print("\nindependence test = %s" % self.cond_ind_test.measure + + "\ntau_min = %d" % tau_min + + "\ntau_max = %d" % tau_max) + +
[docs] def run_fullci(self, + selected_links=None, + tau_min=0, + tau_max=1): + """FullCI conditional independence tests. + + Implements the FullCI test (see [1]_). Returns the matrices of + test statistic values, p-values, and confidence intervals. + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:all_parents (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + tau_min : int, default: 0 + Minimum time lag to test. Note that zero-lags are undirected. + tau_max : int, default: 1 + Maximum time lag. Must be larger or equal to tau_min. + + Returns + ------- + results : dictionary of arrays of shape [N, N, tau_max+1] + {'val_matrix':val_matrix, 'p_matrix':p_matrix} are always returned + and optionally conf_matrix which is of shape [N, N, tau_max+1,2] + """ + # Check the limits on tau + self._check_tau_limits(tau_min, tau_max) + # Set the selected links + _int_sel_links = self._set_sel_links(selected_links, tau_min, tau_max) + # Print information about the input parameters + if self.verbosity > 0: + self._print_fullci_parameters(tau_min, tau_max) + + full_past = dict([(j, [(i, -tau) + for i in range(self.N) + for tau in range(max(1, tau_min), tau_max+1)]) + for j in range(self.N)]) + + # Get the parents that will be checked + _int_parents = self._get_int_parents(full_past) + # Initialize the return values + val_matrix = np.zeros((self.N, self.N, tau_max + 1)) + p_matrix = np.ones((self.N, self.N, tau_max + 1)) + # Initialize the optional return of the confidance matrix + conf_matrix = None + if self.cond_ind_test.confidence is not False: + conf_matrix = np.zeros((self.N, self.N, tau_max + 1, 2)) + + # Get the conditions as implied by the input arguments + for j, i, tau, Z in self._iter_indep_conds(_int_parents, + self.selected_variables, + _int_sel_links, + None, 0): + # Set X and Y (for clarity of code) + X = [(i, tau)] + Y = [(j, 0)] + # Run the independence tests and record the results + val, pval = self.cond_ind_test.run_test(X, Y, Z=Z, + tau_max=tau_max, + cut_off='max_lag') + val_matrix[i, j, abs(tau)] = val + p_matrix[i, j, abs(tau)] = pval + # Get the confidance value, returns None if cond_ind_test.confidance + # is False + conf = self.cond_ind_test.get_confidence(X, Y, Z=Z, tau_max=tau_max) + # Record the value if the conditional independence requires it + if self.cond_ind_test.confidence: + conf_matrix[i, j, abs(tau)] = conf + # Print the results if needed + if self.verbosity > 1: + self.cond_ind_test._print_cond_ind_results(val, + pval=pval, + conf=conf) + # Return the values as a dictionary + return {'val_matrix':val_matrix, + 'p_matrix':p_matrix, + 'conf_matrix':conf_matrix}
+ + def _print_bivci_parameters(self, tau_min, tau_max): + """Print the parameters for this BivCI algorithm + + Parameters + ---------- + tau_min : int + Minimum time delay + tau_max : int + Maximum time delay + """ + print("\n##\n## Running Tigramite BivCI algorithm\n##" + "\n\nParameters:") + print("\nindependence test = %s" % self.cond_ind_test.measure + + "\ntau_min = %d" % tau_min + + "\ntau_max = %d" % tau_max) + +
[docs] def run_bivci(self, + selected_links=None, + tau_min=0, + tau_max=1): + """BivCI conditional independence tests. + + Implements the BivCI test (see [1]_). Returns the matrices of + test statistic values, p-values, and confidence intervals. + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:all_parents (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + tau_min : int, default: 0 + Minimum time lag to test. Note that zero-lags are undirected. + tau_max : int, default: 1 + Maximum time lag. Must be larger or equal to tau_min. + + Returns + ------- + results : dictionary of arrays of shape [N, N, tau_max+1] + {'val_matrix':val_matrix, 'p_matrix':p_matrix} are always returned + and optionally conf_matrix which is of shape [N, N, tau_max+1,2] + """ + # Check the limits on tau + self._check_tau_limits(tau_min, tau_max) + # Set the selected links + _int_sel_links = self._set_sel_links(selected_links, tau_min, tau_max) + # Print information about the input parameters + if self.verbosity > 0: + self._print_bivci_parameters(tau_min, tau_max) + + auto_past = dict([(j, [(j, -tau) + for tau in range(max(1, tau_min), tau_max+1)]) + for j in range(self.N)]) + + # Get the parents that will be checked + _int_parents = self._get_int_parents(auto_past) + # Initialize the return values + val_matrix = np.zeros((self.N, self.N, tau_max + 1)) + p_matrix = np.ones((self.N, self.N, tau_max + 1)) + # Initialize the optional return of the confidance matrix + conf_matrix = None + if self.cond_ind_test.confidence is not False: + conf_matrix = np.zeros((self.N, self.N, tau_max + 1, 2)) + + # Get the conditions as implied by the input arguments + for j, i, tau, Z in self._iter_indep_conds(_int_parents, + self.selected_variables, + _int_sel_links, + None, 0): + # Set X and Y (for clarity of code) + X = [(i, tau)] + Y = [(j, 0)] + # Run the independence tests and record the results + val, pval = self.cond_ind_test.run_test(X, Y, Z=Z, + tau_max=tau_max, + cut_off='max_lag') + val_matrix[i, j, abs(tau)] = val + p_matrix[i, j, abs(tau)] = pval + # Get the confidance value, returns None if cond_ind_test.confidance + # is False + conf = self.cond_ind_test.get_confidence(X, Y, Z=Z, tau_max=tau_max) + # Record the value if the conditional independence requires it + if self.cond_ind_test.confidence: + conf_matrix[i, j, abs(tau)] = conf + # Print the results if needed + if self.verbosity > 1: + self.cond_ind_test._print_cond_ind_results(val, + pval=pval, + conf=conf) + # Return the values as a dictionary + return {'val_matrix':val_matrix, + 'p_matrix':p_matrix, + 'conf_matrix':conf_matrix}
+ +
[docs] def get_corrected_pvalues(self, p_matrix, + fdr_method='fdr_bh', + exclude_contemporaneous=True): + """Returns p-values corrected for multiple testing. + + Currently implemented is Benjamini-Hochberg False Discovery Rate + method. Correction is performed either among all links if + exclude_contemporaneous==False, or only among lagged links. + + Parameters + ---------- + p_matrix : array-like + Matrix of p-values. Must be of shape (N, N, tau_max + 1). + fdr_method : str, optional (default: 'fdr_bh') + Correction method, currently implemented is Benjamini-Hochberg + False Discovery Rate method. + exclude_contemporaneous : bool, optional (default: True) + Whether to include contemporaneous links in correction. + + Returns + ------- + q_matrix : array-like + Matrix of shape (N, N, tau_max + 1) containing corrected p-values. + """ + + def _ecdf(x): + '''no frills empirical cdf used in fdrcorrection + ''' + nobs = len(x) + return np.arange(1,nobs+1)/float(nobs) + + # Get the shape paramters from the p_matrix + _, N, tau_max_plusone = p_matrix.shape + # Create a mask for these values + mask = np.ones((N, N, tau_max_plusone), dtype='bool') + # Ignore values from autocorrelation indices + mask[range(N), range(N), 0] = False + # Exclude all contemporaneous values if requested + if exclude_contemporaneous: + mask[:, :, 0] = False + # Create the return value + q_matrix = np.array(p_matrix) + # Use the multiple tests function + if fdr_method is None or fdr_method == 'none': + pass + elif fdr_method == 'fdr_bh': + pvs = p_matrix[mask] + # q_matrix[mask] = multicomp.multipletests(pvs, method=fdr_method)[1] + + pvals_sortind = np.argsort(pvs) + pvals_sorted = np.take(pvs, pvals_sortind) + + ecdffactor = _ecdf(pvals_sorted) + # reject = pvals_sorted <= ecdffactor*alpha + + pvals_corrected_raw = pvals_sorted / ecdffactor + pvals_corrected = np.minimum.accumulate(pvals_corrected_raw[::-1])[::-1] + del pvals_corrected_raw + + pvals_corrected[pvals_corrected>1] = 1 + pvals_corrected_ = np.empty_like(pvals_corrected) + pvals_corrected_[pvals_sortind] = pvals_corrected + del pvals_corrected + + q_matrix[mask] = pvals_corrected_ + + else: + raise ValueError('Only FDR method fdr_bh implemented') + + # Return the new matrix + return q_matrix
+ +
[docs] def return_significant_parents(self, + pq_matrix, + val_matrix, + alpha_level=0.05, + include_lagzero_parents=False): + """Returns list of significant parents as well as a boolean matrix. + + Significance based on p-matrix, or q-value matrix with corrected + p-values. + + Parameters + ---------- + pq_matrix : array-like + p-matrix, or q-value matrix with corrected p-values. Must be of + shape (N, N, tau_max + 1). + val_matrix : array-like + Matrix of test statistic values. Must be of shape (N, N, tau_max + + 1). + alpha_level : float, optional (default: 0.05) + Significance level. + include_lagzero_parents : bool (default: False) + Whether the parents dictionary should also return parents at lag + zero. Note that the link_matrix always contains those. + + Returns + ------- + all_parents : dict + Dictionary of form {0:[(0, -1), (3, -2), ...], 1:[], ...} + containing estimated parents. + + link_matrix : array, shape [N, N, tau_max+1] + Boolean array with True entries for significant links at alpha_level + """ + # Initialize the return value + all_parents = dict() + for j in self.selected_variables: + # Get the good links + if include_lagzero_parents: + good_links = np.argwhere(pq_matrix[:, j, :] <= alpha_level) + # Build a dictionary from these links to their values + links = {(i, -tau): np.abs(val_matrix[i, j, abs(tau)]) + for i, tau in good_links} + else: + good_links = np.argwhere(pq_matrix[:, j, 1:] <= alpha_level) + # Build a dictionary from these links to their values + links = {(i, -tau-1): np.abs(val_matrix[i, j, abs(tau) + 1]) + for i, tau in good_links} + # Sort by value + all_parents[j] = sorted(links, key=links.get, reverse=True) + # Return the significant parents + return {'parents': all_parents, + 'link_matrix': pq_matrix <= alpha_level}
+ + + +
[docs] def print_results(self, + return_dict, + alpha_level=0.05): + """Prints significant parents from output of MCI or PCMCI algorithms. + + Parameters + ---------- + return_dict : dict + Dictionary of return values, containing keys + * 'p_matrix' + * 'val_matrix' + * 'conf_matrix' + 'q_matrix' can also be included in keys, but is not necessary. + + alpha_level : float, optional (default: 0.05) + Significance level. + """ + # Check if q_matrix is defined. It is returned for PCMCI but not for + # MCI + q_matrix = None + q_key = 'q_matrix' + if q_key in return_dict: + q_matrix = return_dict[q_key] + # Check if conf_matrix is defined + conf_matrix = None + conf_key = 'conf_matrix' + if conf_key in return_dict: + conf_matrix = return_dict[conf_key] + # Wrap the already defined function + self.print_significant_links(return_dict['p_matrix'], + return_dict['val_matrix'], + conf_matrix=conf_matrix, + q_matrix=q_matrix, + alpha_level=alpha_level)
+ +
[docs] def run_pcmci(self, + selected_links=None, + tau_min=0, + tau_max=1, + save_iterations=False, + pc_alpha=0.05, + max_conds_dim=None, + max_combinations=1, + max_conds_py=None, + max_conds_px=None, + fdr_method='none'): + """Run full PCMCI causal discovery for time series datasets. + + Wrapper around PC-algorithm function and MCI function. + + Parameters + ---------- + selected_links : dict or None + Dictionary of form {0:all_parents (3, -2), ...], 1:[], ...} + specifying whether only selected links should be tested. If None is + passed, all links are tested + + tau_min : int, optional (default: 0) + Minimum time lag to test. Note that zero-lags are undirected. + + tau_max : int, optional (default: 1) + Maximum time lag. Must be larger or equal to tau_min. + + save_iterations : bool, optional (default: False) + Whether to save iteration step results such as conditions used. + + pc_alpha : float, optional (default: 0.05) + Significance level in algorithm. + + max_conds_dim : int, optional (default: None) + Maximum number of conditions to test. If None is passed, this number + is unrestricted. + + max_combinations : int, optional (default: 1) + Maximum number of combinations of conditions of current cardinality + to test. Defaults to 1 for PC_1 algorithm. For original PC algorithm + a larger number, such as 10, can be used. + + max_conds_py : int, optional (default: None) + Maximum number of conditions of Y to use. If None is passed, this + number is unrestricted. + + max_conds_px : int, optional (default: None) + Maximum number of conditions of Z to use. If None is passed, this + number is unrestricted. + + fdr_method : str, optional (default: 'none') + Correction method, default is Benjamini-Hochberg False Discovery + Rate method. + + Returns + ------- + results : dictionary of arrays of shape [N, N, tau_max+1] + {'val_matrix':val_matrix, 'p_matrix':p_matrix} are always returned + and optionally q_matrix and conf_matrix which is of shape + [N, N, tau_max+1,2] + """ + # Get the parents from run_pc_stable + all_parents = self.run_pc_stable(selected_links=selected_links, + tau_min=tau_min, + tau_max=tau_max, + save_iterations=save_iterations, + pc_alpha=pc_alpha, + max_conds_dim=max_conds_dim, + max_combinations=max_combinations) + # Get the results from run_mci, using the parents as the input + results = self.run_mci(selected_links=selected_links, + tau_min=tau_min, + tau_max=tau_max, + parents=all_parents, + max_conds_py=max_conds_py, + max_conds_px=max_conds_px) + # Get the values and p-values + val_matrix = results['val_matrix'] + p_matrix = results['p_matrix'] + # Initialize and fill the the confidance matrix if the confidance test + # says it should be returned + + conf_matrix = None + if self.cond_ind_test.confidence is not False: + conf_matrix = results['conf_matrix'] + # Initialize and fill the q_matrix if there is a fdr_method + q_matrix = None + if fdr_method != 'none': + q_matrix = self.get_corrected_pvalues(p_matrix, + fdr_method=fdr_method) + # Store the parents in the pcmci member + self.all_parents = all_parents + # Cache the resulting values in the return dictionary + return_dict = {'val_matrix': val_matrix, + 'p_matrix': p_matrix, + 'q_matrix': q_matrix, + 'conf_matrix': conf_matrix} + # Print the information + if self.verbosity > 0: + self.print_results(return_dict) + # Return the dictionary + return return_dict
+ +if __name__ == '__main__': + from tigramite.independence_tests import ParCorr + import tigramite.data_processing as pp + dataframe = pp.DataFrame(np.random.randn(100,3),) + pcmci = PCMCI(dataframe, ParCorr()) + + pcmci.get_corrected_pvalues(np.random.rand(2,2,2)) +
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/tigramite/plotting.html b/docs/_build/html/_modules/tigramite/plotting.html new file mode 100644 index 00000000..f89e6b16 --- /dev/null +++ b/docs/_build/html/_modules/tigramite/plotting.html @@ -0,0 +1,2514 @@ + + + + + + + tigramite.plotting — Tigramite 4.0 documentation + + + + + + + + + + + + + +
+
+
+
+ +

Source code for tigramite.plotting

+"""Tigramite plotting package."""
+
+# Author: Jakob Runge <jakobrunge@posteo.de>
+#
+# License: GNU General Public License v3.0
+
+import numpy as np
+import matplotlib.transforms as transforms
+from matplotlib import pyplot, ticker
+from matplotlib.ticker import FormatStrFormatter
+
+from copy import deepcopy
+import os
+
+# TODO: Add proper docstrings to internal functions...
+
+
+def _par_corr_trafo(cmi):
+    """Transformation of CMI to partial correlation scale."""
+
+    # Set negative values to small positive number
+    # (zero would be interpreted as non-significant in some functions)
+    if np.ndim(cmi) == 0:
+        if cmi < 0.:
+            cmi = 1E-8
+    else:
+        cmi[cmi < 0.] = 1E-8
+
+    return np.sqrt(1. - np.exp(-2. * cmi))
+
+
+def _par_corr_to_cmi(par_corr):
+    """Transformation of partial correlation to CMI scale."""
+
+    return -0.5 * np.log(1. - par_corr**2)
+
+
+def _myround(x, base=5, round_mode='updown'):
+    """Rounds x to a float with precision base."""
+
+    if round_mode == 'updown':
+        return base * round(float(x) / base)
+    elif round_mode == 'down':
+        return base * np.floor(float(x) / base)
+    elif round_mode == 'up':
+        return base * np.ceil(float(x) / base)
+
+    return base * round(float(x) / base)
+
+
+def _make_nice_axes(ax, where=None, skip=2, color=None):
+    """Makes nice axes."""
+
+    if where is None:
+        where = ['left', 'bottom']
+    if color is None:
+        color = {'left': 'black', 'right': 'black',
+                 'bottom': 'black', 'top': 'black'}
+
+    if type(skip) == int:
+        skip_x = skip_y = skip
+    else:
+        skip_x = skip[0]
+        skip_y = skip[1]
+
+    for loc, spine in ax.spines.items():
+        if loc in where:
+            spine.set_position(('outward', 5))  # outward by 10 points
+            spine.set_color(color[loc])
+            if loc == 'left' or loc == 'right':
+                pyplot.setp(ax.get_yticklines(), color=color[loc])
+                pyplot.setp(ax.get_yticklabels(), color=color[loc])
+            if loc == 'top' or loc == 'bottom':
+                pyplot.setp(ax.get_xticklines(), color=color[loc])
+        elif loc in [item for item in ['left', 'bottom', 'right', 'top']
+                     if item not in where]:
+            spine.set_color('none')  # don't draw spine
+
+        else:
+            raise ValueError('unknown spine location: %s' % loc)
+
+    # ax.xaxis.get_major_formatter().set_useOffset(False)
+
+    # turn off ticks where there is no spine
+    if 'top' in where and 'bottom' not in where:
+        ax.xaxis.set_ticks_position('top')
+        ax.set_xticks(ax.get_xticks()[::skip_x])
+    elif 'bottom' in where:
+        ax.xaxis.set_ticks_position('bottom')
+        ax.set_xticks(ax.get_xticks()[::skip_x])
+    else:
+        ax.xaxis.set_ticks_position('none')
+        ax.xaxis.set_ticklabels([])
+    if 'right' in where and 'left' not in where:
+        ax.yaxis.set_ticks_position('right')
+        ax.set_yticks(ax.get_yticks()[::skip_y])
+    elif 'left' in where:
+        ax.yaxis.set_ticks_position('left')
+        ax.set_yticks(ax.get_yticks()[::skip_y])
+    else:
+        ax.yaxis.set_ticks_position('none')
+        ax.yaxis.set_ticklabels([])
+
+    ax.patch.set_alpha(0.)
+
+
+def _get_absmax(val_matrix):
+    """Get value at absolute maximum in lag function array.
+
+    For an (N, N, tau)-array this comutes the lag of the absolute maximum
+    along the tau-axis and stores the (positive or negative) value in
+    the (N,N)-array absmax."""
+
+    absmax_indices = np.abs(val_matrix).argmax(axis=2)
+    i, j = np.indices(val_matrix.shape[:2])
+
+    return val_matrix[i, j, absmax_indices]
+
+
+def _add_timeseries(fig, axes, i, time, dataseries, label,
+                   use_mask=False,
+                   mask=None,
+                   missing_flag=None,
+                   grey_masked_samples=False,
+                   data_linewidth=1.,
+                   skip_ticks_data_x=1,
+                   skip_ticks_data_y=1,
+                   unit=None,
+                   last=False,
+                   time_label='',
+                   label_fontsize=10,
+                   color='black',
+                   grey_alpha=1.,
+                   ):
+    """Adds a time series plot to an axis.
+
+    Plot of dataseries is added to axis. Allows for proper visualization of
+    masked data.
+
+    Parameters
+    ----------
+    fig : figure instance
+        Figure instance.
+
+    axes : axis instance
+        Either gridded axis object or single axis instance.
+
+    i : int
+        Index of axis in gridded axis object.
+
+    time : array
+        Timelabel array.
+
+    dataseries : array-like
+        One-dimensional data series array of variable.
+
+    missing_flag : number, optional (default: None)
+        Flag for missing values in dataframe. Dismisses all time slices of
+        samples where missing values occur in any variable and also flags
+        samples for all lags up to 2*tau_max. This avoids biases, see section on
+        masking in Supplement of [1]_.
+
+    label : str
+        Variable label.
+
+    use_mask : bool, optional (default: False)
+        Whether to use masked data.
+
+    mask : array-like, optional (default: None)
+        Data mask where True labels masked samples.
+
+    grey_masked_samples : bool, optional (default: False)
+        Whether to mark masked samples by grey fills ('fill') or grey data ('data').
+
+    data_linewidth : float, optional (default: 1.)
+        Linewidth.
+
+    skip_ticks_data_x : int, optional (default: 1)
+        Skip every other tickmark.
+
+    skip_ticks_data_y : int, optional (default: 1)
+        Skip every other tickmark.
+
+    unit : str, optional (default: None)
+        Units of variable.
+
+    last : bool, optional (default: False)
+        Specifiy whether this is the last panel where also the bottom axis is
+        plotted.
+
+    time_label : str, optional (default: '')
+        Label of time axis.
+
+    label_fontsize : int, optional (default: 10)
+        Fontsize.
+
+    color : str, optional (default: black)
+        Line color.
+
+    grey_alpha : float, optional (default: 1.)
+        Opacity of line.
+    """
+
+    # axes[i].xaxis.get_major_formatter().set_useOffset(False)
+    try:
+        ax = axes[i]
+    except:
+        ax = axes
+
+    if missing_flag is not None:
+        dataseries_nomissing = np.ma.masked_where(dataseries==missing_flag,
+                                                 dataseries)
+    else:
+        dataseries_nomissing = np.ma.masked_where(
+                                                 np.zeros(dataseries.shape),
+                                                 dataseries)
+
+    if use_mask:
+
+        maskdata = np.ma.masked_where(mask, dataseries_nomissing)
+
+        if grey_masked_samples == 'fill':
+            ax.fill_between(time, maskdata.min(), maskdata.max(),
+                            where=mask, color='grey',
+                            interpolate=True,
+                            linewidth=0., alpha=grey_alpha)
+        elif grey_masked_samples == 'data':
+            ax.plot(time, dataseries_nomissing,
+                    color='grey', marker='.', markersize=data_linewidth,
+                    linewidth=data_linewidth, clip_on=False,
+                    alpha=grey_alpha)
+
+        ax.plot(time, maskdata,
+                color=color, linewidth=data_linewidth, marker='.',
+                markersize=data_linewidth, clip_on=False)
+    else:
+        ax.plot(time, dataseries_nomissing,
+                color=color, linewidth=data_linewidth, clip_on=False)
+
+    if last:
+        _make_nice_axes(ax, where=['left', 'bottom'], skip=(
+            skip_ticks_data_x, skip_ticks_data_y))
+        ax.set_xlabel(r'%s' % time_label, fontsize=label_fontsize)
+    else:
+        _make_nice_axes(
+            ax, where=['left'], skip=(skip_ticks_data_x, skip_ticks_data_y))
+    # ax.get_xaxis().get_major_formatter().set_useOffset(False)
+
+    ax.xaxis.set_major_formatter(FormatStrFormatter('%.0f'))
+    ax.label_outer()
+
+    ax.set_xlim(time[0], time[-1])
+
+    trans = transforms.blended_transform_factory(
+        fig.transFigure, ax.transAxes)
+    if unit:
+        ax.set_ylabel(r'%s [%s]' % (label, unit), fontsize=label_fontsize)
+    else:
+        ax.set_ylabel(r'%s' % (label), fontsize=label_fontsize)
+
+        # ax.text(.02, .5, r'%s [%s]' % (label, unit), fontsize=label_fontsize,
+        #         horizontalalignment='left', verticalalignment='center',
+        #         rotation=90, transform=trans)
+    # else:
+    #     ax.text(.02, .5, r'%s' % (label), fontsize=label_fontsize,
+    #             horizontalalignment='left', verticalalignment='center',
+    #             rotation=90, transform=trans)
+    pyplot.tight_layout()
+
+
+
[docs]def plot_timeseries(dataframe=None, + save_name=None, + fig_axes=None, + figsize=None, + var_units=None, + time_label='time', + use_mask=False, + grey_masked_samples=False, + data_linewidth=1., + skip_ticks_data_x=1, + skip_ticks_data_y=2, + label_fontsize=8, + ): + """Create and save figure of stacked panels with time series. + + Parameters + ---------- + dataframe : data object, optional + This is the Tigramite dataframe object. It has the attributes + dataframe.values yielding a np array of shape (observations T, + variables N) and optionally a mask of the same shape. + + save_name : str, optional (default: None) + Name of figure file to save figure. If None, figure is shown in window. + + fig_axes : subplots instance, optional (default: None) + Figure and axes instance. If None they are created as + fig, axes = pyplot.subplots(N,...) + + figsize : tuple of floats, optional (default: None) + Figure size if new figure is created. If None, default pyplot figsize + is used. + + var_units : list of str, optional (default: None) + Units of variables. + + time_label : str, optional (default: '') + Label of time axis. + + use_mask : bool, optional (default: False) + Whether to use masked data. + + grey_masked_samples : bool, optional (default: False) + Whether to mark masked samples by grey fills ('fill') or grey data ('data'). + + data_linewidth : float, optional (default: 1.) + Linewidth. + + skip_ticks_data_x : int, optional (default: 1) + Skip every other tickmark. + + skip_ticks_data_y : int, optional (default: 2) + Skip every other tickmark. + + label_fontsize : int, optional (default: 10) + Fontsize of variable labels. + """ + + # Read in all attributes from dataframe + data = dataframe.values + mask = dataframe.mask + var_names = dataframe.var_names + missing_flag = dataframe.missing_flag + datatime = dataframe.datatime + + T, N = data.shape + + if var_units is None: + var_units = ['' for i in range(N)] + + if fig_axes is None: + fig, axes = pyplot.subplots(N, sharex=True, + figsize=figsize) + else: + fig, axes = fig_axes + + for i in range(N): + if mask is None: + mask_i = None + else: + mask_i = mask[:, i] + _add_timeseries(fig=fig, axes=axes, i=i, + time=datatime, + dataseries=data[:, i], + label=var_names[i], + use_mask=use_mask, + mask=mask_i, + missing_flag=missing_flag, + grey_masked_samples=grey_masked_samples, + data_linewidth=data_linewidth, + skip_ticks_data_x=skip_ticks_data_x, + skip_ticks_data_y=skip_ticks_data_y, + unit=var_units[i], + last=(i == N - 1), + time_label=time_label, + label_fontsize=label_fontsize, + ) + + fig.subplots_adjust(bottom=0.15, top=.9, left=0.15, right=.95, hspace=.3) + pyplot.tight_layout() + + if save_name is not None: + fig.savefig(save_name) + else: + return fig, axes
+ +
[docs]def plot_lagfuncs(val_matrix, name=None, setup_args={}, add_lagfunc_args={}): + """Wrapper helper function to plot lag functions. + + Sets up the matrix object and plots the lagfunction, see parameters in setup_matrix + and add_lagfuncs. + + Parameters + ---------- + val_matrix : array_like + Matrix of shape (N, N, tau_max+1) containing test statistic values. + + name : str, optional (default: None) + File name. If None, figure is shown in window. + + setup_args : dict + Arguments for setting up the lag function matrix, see doc of + setup_matrix. + + add_lagfunc_args : dict + Arguments for adding a lag function matrix, see doc of add_lagfuncs. + + Returns + ------- + matrix : object + Further lag functions can be overlaid using the matrix.add_lagfuncs(val_matrix) + function. + """ + + N, N, tau_max_plusone = val_matrix.shape + tau_max = tau_max_plusone - 1 + + matrix = setup_matrix(N=N, tau_max=tau_max, **setup_args) + # **{key: value for key, value in + # kwargs.items() if key + # in setup_matrix.__init__.func_code.co_varnames}) + matrix.add_lagfuncs(val_matrix=val_matrix, **add_lagfunc_args) + # **{key: value for key, value in + # kwargs.items() if key + # in setup_matrix.add_lagfuncs.func_code.co_varnames}) + + if name is not None: + matrix.savefig(name=name) + + return matrix
+ +
[docs]class setup_matrix(): + """Create matrix of lag function panels. + + Class to setup figure object. The function add_lagfuncs(...) allows to plot + the val_matrix of shape (N, N, tau_max+1). Multiple lagfunctions can be + overlaid for comparison. + + Parameters + ---------- + N : int + Number of variables + + tau_max : int + Maximum time lag. + + var_names : list, optional (default: None) + List of variable names. If None, range(N) is used. + + figsize : tuple of floats, optional (default: None) + Figure size if new figure is created. If None, default pyplot figsize + is used. + + minimum : int, optional (default: -1) + Lower y-axis limit. + + maximum : int, optional (default: 1) + Upper y-axis limit. + + label_space_left : float, optional (default: 0.1) + Fraction of horizontal figure space to allocate left of plot for labels. + + label_space_top : float, optional (default: 0.05) + Fraction of vertical figure space to allocate top of plot for labels. + + legend_width : float, optional (default: 0.15) + Fraction of horizontal figure space to allocate right of plot for + legend. + + x_base : float, optional (default: 1.) + x-tick intervals to show. + + y_base : float, optional (default: .4) + y-tick intervals to show. + + plot_gridlines : bool, optional (default: False) + Whether to show a grid. + + lag_units : str, optional (default: '') + + lag_array : array, optional (default: None) + Optional specification of lags overwriting np.arange(0, tau_max+1) + + label_fontsize : int, optional (default: 10) + Fontsize of variable labels. + """ + + def __init__(self, N, tau_max, + var_names=None, + figsize=None, + minimum=-1, + maximum=1, + label_space_left=0.1, + label_space_top=.05, + legend_width=.15, + legend_fontsize=10, + x_base=1., y_base=0.5, + plot_gridlines=False, + lag_units='', + lag_array=None, + label_fontsize=10): + + self.tau_max = tau_max + + self.labels = [] + self.lag_units = lag_units + # if lag_array is None: + # self.lag_array = np.arange(0, self.tau_max + 1) + # else: + self.lag_array = lag_array + if x_base is None: + self.x_base = 1 + else: + self.x_base = x_base + + + self.legend_width = legend_width + self.legend_fontsize = legend_fontsize + + self.label_space_left = label_space_left + self.label_space_top = label_space_top + self.label_fontsize = label_fontsize + + self.fig = pyplot.figure(figsize=figsize) + + self.axes_dict = {} + + if var_names is None: + var_names = range(N) + + plot_index = 1 + for i in range(N): + for j in range(N): + self.axes_dict[(i, j)] = self.fig.add_subplot(N, N, plot_index) + # Plot process labels + if j == 0: + trans = transforms.blended_transform_factory( + self.fig.transFigure, self.axes_dict[(i, j)].transAxes) + self.axes_dict[(i, j)].text(0.01, .5, '%s' % + str(var_names[i]), + fontsize=label_fontsize, + horizontalalignment='left', + verticalalignment='center', + transform=trans) + if i == 0: + trans = transforms.blended_transform_factory( + self.axes_dict[(i, j)].transAxes, self.fig.transFigure) + self.axes_dict[(i, j)].text(.5, .99, r'${\to}$ ' + '%s' % + str(var_names[j]), + fontsize=label_fontsize, + horizontalalignment='center', + verticalalignment='top', + transform=trans) + + # Make nice axis + _make_nice_axes( + self.axes_dict[(i, j)], where=['left', 'bottom'], + skip=(1, 1)) + if x_base is not None: + self.axes_dict[(i, j)].xaxis.set_major_locator( + ticker.FixedLocator(np.arange(0, self.tau_max + 1, + x_base))) + if x_base / 2. % 1 == 0: + self.axes_dict[(i, j)].xaxis.set_minor_locator( + ticker.FixedLocator(np.arange(0, self.tau_max + + 1, + x_base / 2.))) + if y_base is not None: + self.axes_dict[(i, j)].yaxis.set_major_locator( + ticker.FixedLocator( + np.arange(_myround(minimum, y_base, 'down'), + _myround(maximum, y_base, 'up') + + y_base, y_base))) + self.axes_dict[(i, j)].yaxis.set_minor_locator( + ticker.FixedLocator( + np.arange(_myround(minimum, y_base, 'down'), + _myround(maximum, y_base, 'up') + + y_base, y_base / 2.))) + + self.axes_dict[(i, j)].set_ylim( + _myround(minimum, y_base, 'down'), + _myround(maximum, y_base, 'up')) + if j != 0: + self.axes_dict[(i, j)].get_yaxis().set_ticklabels([]) #label_outer() + self.axes_dict[(i, j)].set_xlim(0, self.tau_max) + if plot_gridlines: + self.axes_dict[(i, j)].grid(True, which='major', + color='black', + linestyle='dotted', + dashes=(1, 1), + linewidth=.05, + zorder=-5) + + plot_index += 1 + +
[docs] def add_lagfuncs(self, val_matrix, + sig_thres=None, + conf_matrix=None, + color='black', + label=None, + two_sided_thres=True, + marker='.', + markersize=5, + alpha=1., + ): + """Add lag function plot from val_matrix array. + + Parameters + ---------- + val_matrix : array_like + Matrix of shape (N, N, tau_max+1) containing test statistic values. + + sig_thres : array-like, optional (default: None) + Matrix of significance thresholds. Must be of same shape as + val_matrix. + + conf_matrix : array-like, optional (default: None) + Matrix of shape (, N, tau_max+1, 2) containing confidence bounds. + + color : str, optional (default: 'black') + Line color. + + label : str + Test statistic label. + + two_sided_thres : bool, optional (default: True) + Whether to draw sig_thres for pos. and neg. values. + + marker : matplotlib marker symbol, optional (default: '.') + Marker. + + markersize : int, optional (default: 5) + Marker size. + + alpha : float, optional (default: 1.) + Opacity. + """ + + if label is not None: + self.labels.append((label, color, marker, markersize, alpha)) + + + for ij in list(self.axes_dict): + i = ij[0] + j = ij[1] + maskedres = np.copy(val_matrix[i, j, int(i == j):]) + self.axes_dict[(i, j)].plot(range(int(i == j), self.tau_max + 1), + maskedres, + linestyle='', color=color, + marker=marker, markersize=markersize, + alpha=alpha, clip_on=False) + if conf_matrix is not None: + maskedconfres = np.copy(conf_matrix[i, j, int(i == j):]) + self.axes_dict[(i, j)].plot(range(int(i == j), + self.tau_max + 1), + maskedconfres[:, 0], + linestyle='', color=color, + marker='_', + markersize=markersize - 2, + alpha=alpha, clip_on=False) + self.axes_dict[(i, j)].plot(range(int(i == j), + self.tau_max + 1), + maskedconfres[:, 1], + linestyle='', color=color, + marker='_', + markersize=markersize - 2, + alpha=alpha, clip_on=False) + + self.axes_dict[(i, j)].plot(range(int(i == j), self.tau_max + 1), + np.zeros(self.tau_max + 1 - + int(i == j)), + color='black', linestyle='dotted', + linewidth=.1) + + if sig_thres is not None: + maskedsigres = sig_thres[i, j, int(i == j):] + + self.axes_dict[(i, j)].plot(range(int(i == j), self.tau_max + 1), + maskedsigres, + color=color, linestyle='solid', + linewidth=.1, alpha=alpha) + if two_sided_thres: + self.axes_dict[(i, j)].plot(range(int(i == j), + self.tau_max + 1), + -sig_thres[i, j, int(i == j):], + color=color, linestyle='solid', + linewidth=.1, alpha=alpha)
+ # pyplot.tight_layout() + +
[docs] def savefig(self, name=None): + """Save matrix figure. + + Parameters + ---------- + name : str, optional (default: None) + File name. If None, figure is shown in window. + """ + + # Trick to plot legend + if len(self.labels) > 0: + axlegend = self.fig.add_subplot(111, frameon=False) + axlegend.spines['left'].set_color('none') + axlegend.spines['right'].set_color('none') + axlegend.spines['bottom'].set_color('none') + axlegend.spines['top'].set_color('none') + axlegend.set_xticks([]) + axlegend.set_yticks([]) + + # self.labels.append((label, color, marker, markersize, alpha)) + for item in self.labels: + + label = item[0] + color = item[1] + marker = item[2] + markersize = item[3] + alpha = item[4] + + axlegend.plot([], [], linestyle='', color=color, + marker=marker, markersize=markersize, + label=label, alpha=alpha) + axlegend.legend(loc='upper left', ncol=1, + bbox_to_anchor=(1.05, 0., .1, 1.), + borderaxespad=0, fontsize=self.legend_fontsize + ).draw_frame(False) + + self.fig.subplots_adjust(left=self.label_space_left, right=1. - + self.legend_width, + top=1. - self.label_space_top, + hspace=0.35, wspace=0.35) + pyplot.figtext( + 0.5, 0.01, r'lag $\tau$ [%s]' % self.lag_units, + horizontalalignment='center', fontsize=self.label_fontsize) + else: + self.fig.subplots_adjust( + left=self.label_space_left, right=.95, + top=1. - self.label_space_top, + hspace=0.35, wspace=0.35) + pyplot.figtext( + 0.55, 0.01, r'lag $\tau$ [%s]' % self.lag_units, + horizontalalignment='center', fontsize=self.label_fontsize) + + if self.lag_array is not None: + assert self.lag_array.shape == np.arange(self.tau_max + 1).shape + for ij in list(self.axes_dict): + i = ij[0] + j = ij[1] + self.axes_dict[(i, j)].set_xticklabels(self.lag_array[::self.x_base]) + + if name is not None: + self.fig.savefig(name) + else: + pyplot.show()
+ + +def _draw_network_with_curved_edges( + fig, ax, + G, pos, + node_rings, + node_labels, node_label_size, node_alpha=1., standard_size=100, + standard_cmap='OrRd', standard_color='grey', log_sizes=False, + cmap_links='YlOrRd', cmap_links_edges='YlOrRd', links_vmin=0., + links_vmax=1., links_edges_vmin=0., links_edges_vmax=1., + links_ticks=.2, links_edges_ticks=.2, link_label_fontsize=8, + arrowstyle='simple', arrowhead_size=3., curved_radius=.2, label_fontsize=4, + label_fraction=.5, link_colorbar_label='link', + link_edge_colorbar_label='link_edge', + undirected_curved=False, undirected_style='solid', + network_lower_bound=0.2, show_colorbar=True, + ): + """Function to draw a network from networkx graph instance. + + Various attributes are used to specify the graph's properties. + + This function is just a beta-template for now that can be further + customized. + """ + + from matplotlib.patches import FancyArrowPatch, Circle + + ax.spines['left'].set_color('none') + ax.spines['right'].set_color('none') + ax.spines['bottom'].set_color('none') + ax.spines['top'].set_color('none') + ax.set_xticks([]) + ax.set_yticks([]) + + N = len(G) + + def draw_edge(ax, u, v, d, seen, arrowstyle='simple', directed=True): + + + + # avoiding attribute error raised by changes in networkx + if hasattr(G, 'node'): + # works with networkx 1.10 + n1 = G.node[u]['patch'] + n2 = G.node[v]['patch'] + else: + # works with networkx 2.4 + n1 = G.nodes[u]['patch'] + n2 = G.nodes[v]['patch'] + + if directed: + rad = -1.*curved_radius +# facecolor = d['directed_color'] +# edgecolor = d['directed_edgecolor'] + if cmap_links is not None: + facecolor = data_to_rgb_links.to_rgba(d['directed_color']) + else: + if d['directed_color'] is not None: + facecolor = d['directed_color'] + else: + facecolor = standard_color + if d['directed_edgecolor'] is not None: + edgecolor = data_to_rgb_links_edges.to_rgba( + d['directed_edgecolor']) + else: + if d['directed_edgecolor'] is not None: + edgecolor = d['directed_edgecolor'] + else: + edgecolor = standard_color + width = d['directed_width'] + alpha = d['directed_alpha'] + if (u, v) in seen: + rad = seen.get((u, v)) + rad = (rad + np.sign(rad) * 0.1) * -1. + arrowstyle = arrowstyle + link_edge = d['directed_edge'] + linestyle = 'solid' + linewidth = 0. + if d.get('directed_attribute', None) == 'spurious': + facecolor = 'grey' + # linestyle = 'dashed' + + else: + rad = undirected_curved * curved_radius + if cmap_links is not None: + facecolor = data_to_rgb_links.to_rgba(d['undirected_color']) + else: + if d['undirected_color'] is not None: + facecolor = d['undirected_color'] + else: + facecolor = standard_color + if d['undirected_edgecolor'] is not None: + edgecolor = data_to_rgb_links_edges.to_rgba( + d['undirected_edgecolor']) + else: + if d['undirected_edgecolor'] is not None: + edgecolor = d['undirected_edgecolor'] + else: + edgecolor = standard_color + + width = d['undirected_width'] + alpha = d['undirected_alpha'] + arrowstyle = 'simple,head_length=0.0001' + link_edge = d['undirected_edge'] + linestyle = undirected_style + linewidth = 0. + if d.get('undirected_attribute', None) == 'spurious': + facecolor = 'grey' + # linestyle = 'dashed' + + # print d['undirected_attribute'] + + if link_edge: + # Outer arrow + e = FancyArrowPatch(n1.center, n2.center, # patchA=n1,patchB=n2, + arrowstyle=arrowstyle, + connectionstyle='arc3,rad=%s' % rad, + mutation_scale=3 * width, + lw=0., + alpha=alpha, + color=edgecolor, + # zorder = -1, + clip_on=False, + patchA=n1, patchB=n2) + + ax.add_patch(e) + # Inner arrow + # print linestyle + e = FancyArrowPatch(n1.center, n2.center, # patchA=n1,patchB=n2, + arrowstyle= arrowstyle, #arrowstyle, + connectionstyle='arc3,rad=%s' % rad, + mutation_scale=width, + lw=linewidth, + alpha=alpha, + linestyle=linestyle, + color=facecolor, + clip_on=False, + patchA=n1, patchB=n2 + ) + ax.add_patch(e) + + if d['label'] is not None and directed: + # Attach labels of lags + trans = None # patch.get_transform() + path = e.get_path() + verts = path.to_polygons(trans)[0] + if len(verts) > 2: + label_vert = verts[1, :] + l = d['label'] + string = str(l) + ax.text(label_vert[0], label_vert[1], string, + fontsize=link_label_fontsize, + verticalalignment='center', + horizontalalignment='center') + + return rad + + # Fix lower left and upper right corner (networkx unfortunately rescales + # the positions...) + # c = Circle((0, 0), radius=.01, alpha=1., fill=False, + # linewidth=0., transform=fig.transFigure) + # ax.add_patch(c) + # c = Circle((1, 1), radius=.01, alpha=1., fill=False, + # linewidth=0., transform=fig.transFigure) + # ax.add_patch(c) + + ## + # Draw nodes + ## + node_sizes = np.zeros((len(node_rings), N)) + for ring in list(node_rings): # iterate through to get all node sizes + if node_rings[ring]['sizes'] is not None: + node_sizes[ring] = node_rings[ring]['sizes'] + else: + node_sizes[ring] = standard_size + + max_sizes = node_sizes.max(axis=1) + total_max_size = node_sizes.sum(axis=0).max() + node_sizes /= total_max_size + node_sizes *= standard_size +# print 'node_sizes ', node_sizes + + # start drawing the outer ring first... + for ring in list(node_rings)[::-1]: + # print ring + # dictionary of rings: {0:{'sizes':(N,)-array, 'color_array':(N,)-array + # or None, 'cmap':string, 'vmin':float or None, 'vmax':float or None}} + if node_rings[ring]['color_array'] is not None: + color_data = node_rings[ring]['color_array'] + if node_rings[ring]['vmin'] is not None: + vmin = node_rings[ring]['vmin'] + else: + vmin = node_rings[ring]['color_array'].min() + if node_rings[ring]['vmax'] is not None: + vmax = node_rings[ring]['vmax'] + else: + vmax = node_rings[ring]['color_array'].max() + if node_rings[ring]['cmap'] is not None: + cmap = node_rings[ring]['cmap'] + else: + cmap = standard_cmap + data_to_rgb = pyplot.cm.ScalarMappable( + norm=None, cmap=pyplot.get_cmap(cmap)) + data_to_rgb.set_array(color_data) + data_to_rgb.set_clim(vmin=vmin, vmax=vmax) + colors = [data_to_rgb.to_rgba(color_data[n]) for n in G] + + if node_rings[ring]['colorbar']: + # Create colorbars for nodes + # cax_n = pyplot.axes([.8 + ring*0.11, + # ax.figbox.bounds[1]+0.05, 0.025, 0.35], frameon=False) # + # setup colorbar axes. + # setup colorbar axes. + cax_n = pyplot.axes([0.05, ax.figbox.bounds[1] + 0.02 + + ring * 0.11, + 0.4, 0.025 + + (len(node_rings) == 1) * 0.035], + frameon=False) + cb_n = pyplot.colorbar( + data_to_rgb, cax=cax_n, orientation='horizontal') + try: + cb_n.set_ticks(np.arange(_myround(vmin, + node_rings[ring]['ticks'], 'down'), _myround( + vmax, node_rings[ring]['ticks'], 'up') + + node_rings[ring]['ticks'], node_rings[ring]['ticks'])) + except: + print ('no ticks given') + cb_n.outline.remove() + # cb_n.set_ticks() + cax_n.set_xlabel( + node_rings[ring]['label'], labelpad=1, + fontsize=label_fontsize) + else: + colors = None + vmin = None + vmax = None + + for n in G: + # if n==1: print node_sizes[:ring+1].sum(axis=0)[n] + + if type(node_alpha) == dict: + alpha = node_alpha[n] + else: + alpha = 1. + + if colors is None: + ax.scatter(pos[n][0], pos[n][1], + s=node_sizes[:ring + 1].sum(axis=0)[n] ** 2, + facecolors=standard_color, + edgecolors=standard_color, alpha=alpha, + clip_on=False, linewidth=.1, zorder=-ring) + else: + ax.scatter(pos[n][0], pos[n][1], + s=node_sizes[:ring + 1].sum(axis=0)[n] ** 2, + facecolors=colors[n], edgecolors='white', + alpha=alpha, + clip_on=False, linewidth=.1, zorder=-ring) + + if ring == 0: + ax.text(pos[n][0], pos[n][1], node_labels[n], + fontsize=node_label_size, + horizontalalignment='center', + verticalalignment='center', alpha=alpha) + + if node_rings[ring]['sizes'] is not None: + # Draw reference node as legend + ax.scatter(0., 0., s=node_sizes[:ring + 1].sum(axis=0).max() ** 2, + alpha=1., facecolors='none', edgecolors='grey', + clip_on=False, linewidth=.1, zorder=-ring) + + if log_sizes: + ax.text(0., 0., ' ' * ring + '%.2f' % + (np.exp(max_sizes[ring]) - 1.), + fontsize=node_label_size, + horizontalalignment='left', verticalalignment='center') + else: + ax.text(0., 0., ' ' * ring + '%.2f' % max_sizes[ring], + fontsize=node_label_size, + horizontalalignment='left', verticalalignment='center') + + ## + # Draw edges of different types + ## + # First draw small circles as anchorpoints of the curved edges + for n in G: + # , transform = ax.transAxes) + size = standard_size*.3 + c = Circle(pos[n], radius=size, alpha=0., fill=False, linewidth=0.) + ax.add_patch(c) + + # avoiding attribute error raised by changes in networkx + if hasattr(G, 'node'): + # works with networkx 1.10 + G.node[n]['patch'] = c + else: + # works with networkx 2.4 + G.nodes[n]['patch'] = c + + # Collect all edge weights to get color scale + all_links_weights = [] + all_links_edge_weights = [] + for (u, v, d) in G.edges(data=True): + if u != v: + if d['directed'] and d['directed_color'] is not None: + all_links_weights.append(d['directed_color']) + if d['undirected'] and d['undirected_color'] is not None: + all_links_weights.append(d['undirected_color']) + if d['directed_edge'] and d['directed_edgecolor'] is not None: + all_links_edge_weights.append(d['directed_edgecolor']) + if d['undirected_edge'] and d['undirected_edgecolor'] is not None: + all_links_edge_weights.append(d['undirected_edgecolor']) + + if cmap_links is not None and len(all_links_weights) > 0: + if links_vmin is None: + links_vmin = np.array(all_links_weights).min() + if links_vmax is None: + links_vmax = np.array(all_links_weights).max() + data_to_rgb_links = pyplot.cm.ScalarMappable( + norm=None, cmap=pyplot.get_cmap(cmap_links)) + data_to_rgb_links.set_array(np.array(all_links_weights)) + data_to_rgb_links.set_clim(vmin=links_vmin, vmax=links_vmax) + # Create colorbars for links +# cax_e = pyplot.axes([.8, ax.figbox.bounds[1]+0.5, 0.025, 0.35], +# frameon=False) # setup colorbar axes. + # setup colorbar axes. + if show_colorbar: + cax_e = pyplot.axes([0.55, ax.figbox.bounds[1] + 0.02, 0.4, 0.025 + + (len(all_links_edge_weights) == 0) * 0.035], + frameon=False) + + cb_e = pyplot.colorbar( + data_to_rgb_links, cax=cax_e, orientation='horizontal') + try: + cb_e.set_ticks(np.arange(_myround(links_vmin, links_ticks, 'down'), + _myround(links_vmax, links_ticks, 'up') + + links_ticks, links_ticks)) + except: + print ('no ticks given') + + cb_e.outline.remove() + # cb_n.set_ticks() + cax_e.set_xlabel( + link_colorbar_label, labelpad=1, fontsize=label_fontsize) + + if cmap_links_edges is not None and len(all_links_edge_weights) > 0: + if links_edges_vmin is None: + links_edges_vmin = np.array(all_links_edge_weights).min() + if links_edges_vmax is None: + links_edges_vmax = np.array(all_links_edge_weights).max() + data_to_rgb_links_edges = pyplot.cm.ScalarMappable( + norm=None, cmap=pyplot.get_cmap(cmap_links_edges)) + data_to_rgb_links_edges.set_array(np.array(all_links_edge_weights)) + data_to_rgb_links_edges.set_clim( + vmin=links_edges_vmin, vmax=links_edges_vmax) + + # Create colorbars for link edges +# cax_e = pyplot.axes([.8+.1, ax.figbox.bounds[1]+0.5, 0.025, 0.35], +# frameon=False) # setup colorbar axes. + # setup colorbar axes. + cax_e = pyplot.axes( + [0.55, ax.figbox.bounds[1] + 0.05 + 0.1, 0.4, 0.025], + frameon=False) + + cb_e = pyplot.colorbar( + data_to_rgb_links_edges, cax=cax_e, orientation='horizontal') + try: + cb_e.set_ticks(np.arange(_myround(links_edges_vmin, + links_edges_ticks, 'down'), + _myround(links_edges_vmax, + links_edges_ticks, 'up') + + links_edges_ticks, + links_edges_ticks)) + except: + print ('no ticks given') + cb_e.outline.remove() + # cb_n.set_ticks() + cax_e.set_xlabel( + link_edge_colorbar_label, labelpad=1, fontsize=label_fontsize) + + # Draw edges + seen = {} + for (u, v, d) in G.edges(data=True): + if u != v: + if d['directed']: + seen[(u, v)] = draw_edge(ax, u, v, d, seen, arrowstyle, directed=True) + if d['undirected'] and (v, u) not in seen: + seen[(u, v)] = draw_edge(ax, u, v, d, seen, directed=False) + + # pyplot.tight_layout() + pyplot.subplots_adjust(bottom=network_lower_bound) + +
[docs]def plot_graph(val_matrix, + var_names=None, + fig_ax=None, + figsize=None, + sig_thres=None, + link_matrix=None, + save_name=None, + link_colorbar_label='MCI', + node_colorbar_label='auto-MCI', + link_width=None, + link_attribute=None, + node_pos=None, + arrow_linewidth=30., + vmin_edges=-1, + vmax_edges=1., + edge_ticks=.4, + cmap_edges='RdBu_r', + vmin_nodes=0, + vmax_nodes=1., + node_ticks=.4, + cmap_nodes='OrRd', + node_size=20, + arrowhead_size=20, + curved_radius=.2, + label_fontsize=10, + alpha=1., + node_label_size=10, + link_label_fontsize=6, + lag_array=None, + network_lower_bound=0.2, + show_colorbar=True, + ): + """Creates a network plot. + + This is still in beta. The network is defined either from True values in + link_matrix, or from thresholding the val_matrix with sig_thres. Nodes + denote variables, straight links contemporaneous dependencies and curved + arrows lagged dependencies. The node color denotes the maximal absolute + auto-dependency and the link color the value at the lag with maximal + absolute cross-dependency. The link label lists the lags with significant + dependency in order of absolute magnitude. The network can also be plotted + over a map drawn before on the same axis. Then the node positions can be + supplied in appropriate axis coordinates via node_pos. + + Parameters + ---------- + val_matrix : array_like + Matrix of shape (N, N, tau_max+1) containing test statistic values. + + var_names : list, optional (default: None) + List of variable names. If None, range(N) is used. + + fig_ax : tuple of figure and axis object, optional (default: None) + Figure and axes instance. If None they are created. + + figsize : tuple + Size of figure. + + sig_thres : array-like, optional (default: None) + Matrix of significance thresholds. Must be of same shape as val_matrix. + Either sig_thres or link_matrix has to be provided. + + link_matrix : bool array-like, optional (default: None) + Matrix of significant links. Must be of same shape as val_matrix. Either + sig_thres or link_matrix has to be provided. + + save_name : str, optional (default: None) + Name of figure file to save figure. If None, figure is shown in window. + + link_colorbar_label : str, optional (default: 'MCI') + Test statistic label. + + node_colorbar_label : str, optional (default: 'auto-MCI') + Test statistic label for auto-dependencies. + + link_width : array-like, optional (default: None) + Array of val_matrix.shape specifying relative link width with maximum + given by arrow_linewidth. If None, all links have same width. + + link_attribute : array-like, optional (default: None) + String array of val_matrix.shape specifying link attributes. + + node_pos : dictionary, optional (default: None) + Dictionary of node positions in axis coordinates of form + node_pos = {'x':array of shape (N,), 'y':array of shape(N)}. These + coordinates could have been transformed before for basemap plots. + + arrow_linewidth : float, optional (default: 30) + Linewidth. + + vmin_edges : float, optional (default: -1) + Link colorbar scale lower bound. + + vmax_edges : float, optional (default: 1) + Link colorbar scale upper bound. + + edge_ticks : float, optional (default: 0.4) + Link tick mark interval. + + cmap_edges : str, optional (default: 'RdBu_r') + Colormap for links. + + vmin_nodes : float, optional (default: 0) + Node colorbar scale lower bound. + + vmax_nodes : float, optional (default: 1) + Node colorbar scale upper bound. + + node_ticks : float, optional (default: 0.4) + Node tick mark interval. + + cmap_nodes : str, optional (default: 'OrRd') + Colormap for links. + + node_size : int, optional (default: 20) + Node size. + + arrowhead_size : int, optional (default: 20) + Size of link arrow head. Passed on to FancyArrowPatch object. + + curved_radius, float, optional (default: 0.2) + Curvature of links. Passed on to FancyArrowPatch object. + + label_fontsize : int, optional (default: 10) + Fontsize of colorbar labels. + + alpha : float, optional (default: 1.) + Opacity. + + node_label_size : int, optional (default: 10) + Fontsize of node labels. + + link_label_fontsize : int, optional (default: 6) + Fontsize of link labels. + + lag_array : array, optional (default: None) + Optional specification of lags overwriting np.arange(0, tau_max+1) + + network_lower_bound : float, optional (default: 0.2) + Fraction of vertical space below graph plot. + + show_colorbar : bool + Whether to show colorbars for links and nodes. + """ + import networkx + + + if fig_ax is None: + fig = pyplot.figure(figsize=figsize) + ax = fig.add_subplot(111, frame_on=False) + else: + fig, ax = fig_ax + + if sig_thres is None and link_matrix is None: + raise ValueError("Need to specify either sig_thres or link_matrix") + + elif sig_thres is not None and link_matrix is None: + link_matrix = np.abs(val_matrix) >= sig_thres + + + if link_width is not None and not np.all(link_width >= 0.): + raise ValueError("link_width must be non-negative") + + N, N, dummy = val_matrix.shape + tau_max = dummy - 1 + + if var_names is None: + var_names = range(N) + + # Define graph links by absolute maximum (positive or negative like for + # partial correlation) + # val_matrix[np.abs(val_matrix) < sig_thres] = 0. + + net = _get_absmax(val_matrix * link_matrix) + G = networkx.DiGraph(net) + + node_color = np.zeros(N) + # list of all strengths for color map + all_strengths = [] + # Add attributes, contemporaneous and directed links are handled separately + for (u, v, dic) in G.edges(data=True): + # average lagfunc for link u --> v ANDOR u -- v + if tau_max > 0: + # argmax of absolute maximum + argmax = np.abs(val_matrix[u, v][1:]).argmax() + 1 + else: + argmax = 0 + if u != v: + # For contemp links masking or finite samples can lead to different + # values for u--v and v--u + # Here we use the maximum for the width and weight (=color) + # of the link + # Draw link if u--v OR v--u at lag 0 is nonzero + # dic['undirected'] = ((np.abs(val_matrix[u, v][0]) >= + # sig_thres[u, v][0]) or + # (np.abs(val_matrix[v, u][0]) >= + # sig_thres[v, u][0])) + dic['undirected'] = (link_matrix[u,v,0] or link_matrix[v,u,0]) + dic['undirected_alpha'] = alpha + # value at argmax of average + if np.abs(val_matrix[u, v][0] - val_matrix[v, u][0]) > .0001: + print("Contemporaneous I(%d; %d)=%.3f != I(%d; %d)=%.3f" % ( + u, v, val_matrix[u, v][0], v, u, val_matrix[v, u][0]) + + " due to conditions, finite sample effects or " + "masking, here edge color = " + "larger (absolute) value.") + dic['undirected_color'] = _get_absmax( + np.array([[[val_matrix[u, v][0], + val_matrix[v, u][0]]]])).squeeze() + if link_width is None: + dic['undirected_width'] = arrow_linewidth + else: + dic['undirected_width'] = link_width[ + u, v, 0] / link_width.max() * arrow_linewidth + + if link_attribute is None: + dic['undirected_attribute'] = None + else: + dic['undirected_attribute'] = link_attribute[ + u, v, 0] + + # # fraction of nonzero values + dic['undirected_style'] = 'solid' + # else: + # dic['undirected_style'] = link_style[ + # u, v, 0] + + all_strengths.append(dic['undirected_color']) + + if tau_max > 0: + # True if ensemble mean at lags > 0 is nonzero + # dic['directed'] = np.any( + # np.abs(val_matrix[u, v][1:]) >= sig_thres[u, v][1:]) + dic['directed'] = np.any(link_matrix[u,v,1:]) + else: + dic['directed'] = False + + dic['directed_alpha'] = alpha + if link_width is None: + # fraction of nonzero values + dic['directed_width'] = arrow_linewidth + else: + dic['directed_width'] = link_width[ + u, v, argmax] / link_width.max() * arrow_linewidth + + if link_attribute is None: + # fraction of nonzero values + dic['directed_attribute'] = None + else: + dic['directed_attribute'] = link_attribute[ + u, v, argmax] + + # value at argmax of average + dic['directed_color'] = val_matrix[u, v][argmax] + all_strengths.append(dic['directed_color']) + + # Sorted list of significant lags (only if robust wrt + # d['min_ensemble_frac']) + if tau_max > 0: + lags = np.abs(val_matrix[u, v][1:]).argsort()[::-1] + 1 + sig_lags = (np.where(link_matrix[u, v,1:])[0] + 1).tolist() + else: + lags, sig_lags = [], [] + if lag_array is not None: + dic['label'] = str([lag_array[l] for l in lags if l in sig_lags])[1:-1] + else: + dic['label'] = str([l for l in lags if l in sig_lags])[1:-1] + else: + # Node color is max of average autodependency + node_color[u] = val_matrix[u, v][argmax] + dic['undirected_attribute'] = None + dic['directed_attribute'] = None + + + dic['directed_edge'] = False + dic['directed_edgecolor'] = None + dic['undirected_edge'] = False + dic['undirected_edgecolor'] = None + + # If no links are present, set value to zero + if len(all_strengths) == 0: + all_strengths = [0.] + + if node_pos is None: + pos = networkx.circular_layout(deepcopy(G)) +# pos = networkx.spring_layout(deepcopy(G)) + else: + pos = {} + for i in range(N): + pos[i] = (node_pos['x'][i], node_pos['y'][i]) + + if cmap_nodes is None: + node_color = None + + node_rings = {0: {'sizes': None, 'color_array': node_color, + 'cmap': cmap_nodes, 'vmin': vmin_nodes, + 'vmax': vmax_nodes, 'ticks': node_ticks, + 'label': node_colorbar_label, 'colorbar': show_colorbar, + } + } + + _draw_network_with_curved_edges( + fig=fig, ax=ax, + G=deepcopy(G), pos=pos, + # dictionary of rings: {0:{'sizes':(N,)-array, 'color_array':(N,)-array + # or None, 'cmap':string, + node_rings=node_rings, + # 'vmin':float or None, 'vmax':float or None, 'label':string or None}} + node_labels=var_names, node_label_size=node_label_size, + node_alpha=alpha, standard_size=node_size, + standard_cmap='OrRd', standard_color='orange', + log_sizes=False, + cmap_links=cmap_edges, links_vmin=vmin_edges, + links_vmax=vmax_edges, links_ticks=edge_ticks, + + cmap_links_edges='YlOrRd', links_edges_vmin=-1., links_edges_vmax=1., + links_edges_ticks=.2, link_edge_colorbar_label='link_edge', + + arrowstyle='simple', arrowhead_size=arrowhead_size, + curved_radius=curved_radius, label_fontsize=label_fontsize, + link_label_fontsize=link_label_fontsize, + link_colorbar_label=link_colorbar_label, + network_lower_bound=network_lower_bound, + show_colorbar=show_colorbar, + # label_fraction=label_fraction, + # undirected_style=undirected_style + ) + + # fig.subplots_adjust(left=0.1, right=.9, bottom=.25, top=.95) + # savestring = os.path.expanduser(save_name) + if save_name is not None: + pyplot.savefig(save_name) + else: + return fig, ax
+ + +
[docs]def plot_time_series_graph(val_matrix, + var_names=None, + fig_ax=None, + figsize=None, + sig_thres=None, + link_matrix=None, + link_colorbar_label='MCI', + save_name=None, + link_width=None, + arrow_linewidth=20., + vmin_edges=-1, + vmax_edges=1., + edge_ticks=.4, + cmap_edges='RdBu_r', + order=None, + node_size=10, + arrowhead_size=20, + curved_radius=.2, + label_fontsize=10, + alpha=1., + node_label_size=10, + label_space_left=0.1, + label_space_top=0., + network_lower_bound=0.2, + undirected_style='dashed' + ): + """Creates a time series graph. + + This is still in beta. The time series graph's links are colored by + val_matrix. + + Parameters + ---------- + val_matrix : array_like + Matrix of shape (N, N, tau_max+1) containing test statistic values. + + var_names : list, optional (default: None) + List of variable names. If None, range(N) is used. + + fig_ax : tuple of figure and axis object, optional (default: None) + Figure and axes instance. If None they are created. + + figsize : tuple + Size of figure. + + sig_thres : array-like, optional (default: None) + Matrix of significance thresholds. Must be of same shape as val_matrix. + Either sig_thres or link_matrix has to be provided. + + link_matrix : bool array-like, optional (default: None) + Matrix of significant links. Must be of same shape as val_matrix. Either + sig_thres or link_matrix has to be provided. + + save_name : str, optional (default: None) + Name of figure file to save figure. If None, figure is shown in window. + + link_colorbar_label : str, optional (default: 'MCI') + Test statistic label. + + link_width : array-like, optional (default: None) + Array of val_matrix.shape specifying relative link width with maximum + given by arrow_linewidth. If None, all links have same width. + order : list, optional (default: None) + order of variables from top to bottom. + + arrow_linewidth : float, optional (default: 30) + Linewidth. + + vmin_edges : float, optional (default: -1) + Link colorbar scale lower bound. + + vmax_edges : float, optional (default: 1) + Link colorbar scale upper bound. + + edge_ticks : float, optional (default: 0.4) + Link tick mark interval. + + cmap_edges : str, optional (default: 'RdBu_r') + Colormap for links. + + node_size : int, optional (default: 20) + Node size. + + arrowhead_size : int, optional (default: 20) + Size of link arrow head. Passed on to FancyArrowPatch object. + + curved_radius, float, optional (default: 0.2) + Curvature of links. Passed on to FancyArrowPatch object. + + label_fontsize : int, optional (default: 10) + Fontsize of colorbar labels. + + alpha : float, optional (default: 1.) + Opacity. + + node_label_size : int, optional (default: 10) + Fontsize of node labels. + + link_label_fontsize : int, optional (default: 6) + Fontsize of link labels. + + label_space_left : float, optional (default: 0.1) + Fraction of horizontal figure space to allocate left of plot for labels. + + label_space_top : float, optional (default: 0.) + Fraction of vertical figure space to allocate top of plot for labels. + + network_lower_bound : float, optional (default: 0.2) + Fraction of vertical space below graph plot. + + undirected_style : string, optional (default: 'dashed') + Style of undirected contemporaneous links. + """ + + import networkx + + + if fig_ax is None: + fig = pyplot.figure(figsize=figsize) + ax = fig.add_subplot(111, frame_on=False) + else: + fig, ax = fig_ax + + if sig_thres is None and link_matrix is None: + raise ValueError("Need to specify either sig_thres or link_matrix") + + elif sig_thres is not None and link_matrix is None: + link_matrix = np.abs(val_matrix) >= sig_thres + + + if link_width is not None and not np.all(link_width >= 0.): + raise ValueError("link_width must be non-negative") + + N, N, dummy = val_matrix.shape + tau_max = dummy - 1 + max_lag = tau_max + 1 + + if var_names is None: + var_names = range(N) + + + if order is None: + order = range(N) + + if set(order) != set(range(N)): + raise ValueError("order must be a permutation of range(N)") + + def translate(row, lag): + return row * max_lag + lag + + # Define graph links by absolute maximum (positive or negative like for + # partial correlation) + tsg = np.zeros((N * max_lag, N * max_lag)) + tsg_attr = np.zeros((N * max_lag, N * max_lag)) + + for i, j, tau in np.column_stack(np.where(link_matrix)): + # print '\n',i, j, tau + # print np.where(nonmasked[:,j])[0] + + for t in range(max_lag): + if (0 <= translate(i, t - tau) and + translate(i, t - tau) % max_lag <= translate(j, t) % max_lag): + # print translate(i, t-tau), translate(j, t), val_matrix[i,j,tau] + tsg[translate(i, t - tau), translate(j, t) + ] = val_matrix[i, j, tau] + tsg_attr[translate(i, t - tau), translate(j, t) + ] = val_matrix[i, j, tau] + + G = networkx.DiGraph(tsg) + + # node_color = np.zeros(N) + # list of all strengths for color map + all_strengths = [] + # Add attributes, contemporaneous and directed links are handled separately + for (u, v, dic) in G.edges(data=True): + + dic['directed_attribute'] = None + + if u != v: + + if u % max_lag == v % max_lag: + dic['undirected'] = True + dic['directed'] = False + else: + dic['undirected'] = False + dic['directed'] = True + + dic['undirected_alpha'] = alpha + dic['undirected_color'] = _get_absmax( + np.array([[[tsg_attr[u, v], + tsg_attr[v, u]]]]) + ).squeeze() + dic['undirected_width'] = arrow_linewidth + all_strengths.append(dic['undirected_color']) + + dic['directed_alpha'] = alpha + + dic['directed_width'] = arrow_linewidth + + # value at argmax of average + dic['directed_color'] = tsg_attr[u, v] + all_strengths.append(dic['directed_color']) + dic['label'] = None + + dic['directed_edge'] = False + dic['directed_edgecolor'] = None + dic['undirected_edge'] = False + dic['undirected_edgecolor'] = None + + # If no links are present, set value to zero + if len(all_strengths) == 0: + all_strengths = [0.] + + posarray = np.zeros((N * max_lag, 2)) + for i in range(N * max_lag): + + posarray[i] = np.array([(i % max_lag), (1. - i // max_lag)]) + + pos_tmp = {} + for i in range(N * max_lag): + # for n in range(N): + # for tau in range(max_lag): + # i = n*N + tau + pos_tmp[i] = np.array([((i % max_lag) - posarray.min(axis=0)[0]) / + (posarray.max(axis=0)[0] - + posarray.min(axis=0)[0]), + ((1. - i // max_lag) - + posarray.min(axis=0)[1]) / + (posarray.max(axis=0)[1] - + posarray.min(axis=0)[1])]) + + pos = {} + for n in range(N): + for tau in range(max_lag): + pos[n * max_lag + tau] = pos_tmp[order[n] * max_lag + tau] + + node_rings = {0: {'sizes': None, 'color_array': None, + 'label': '', 'colorbar': False, + } + } + + # ] for v in range(max_lag)] + node_labels = ['' for i in range(N * max_lag)] + + _draw_network_with_curved_edges( + fig=fig, ax=ax, + G=deepcopy(G), pos=pos, + # dictionary of rings: {0:{'sizes':(N,)-array, 'color_array':(N,)-array + # or None, 'cmap':string, + node_rings=node_rings, + # 'vmin':float or None, 'vmax':float or None, 'label':string or None}} + node_labels=node_labels, node_label_size=node_label_size, + node_alpha=alpha, standard_size=node_size, + standard_cmap='OrRd', standard_color='grey', + log_sizes=False, + cmap_links=cmap_edges, links_vmin=vmin_edges, + links_vmax=vmax_edges, links_ticks=edge_ticks, + + cmap_links_edges='YlOrRd', links_edges_vmin=-1., links_edges_vmax=1., + links_edges_ticks=.2, link_edge_colorbar_label='link_edge', + + arrowstyle='simple', arrowhead_size=arrowhead_size, + curved_radius=curved_radius, label_fontsize=label_fontsize, + label_fraction=.5, + link_colorbar_label=link_colorbar_label, undirected_curved=True, + network_lower_bound=network_lower_bound, + undirected_style=undirected_style + ) + + for i in range(N): + trans = transforms.blended_transform_factory( + fig.transFigure, ax.transData) + ax.text(label_space_left, pos[order[i] * max_lag][1], + '%s' % str(var_names[order[i]]), fontsize=label_fontsize, + horizontalalignment='left', verticalalignment='center', + transform=trans) + + for tau in np.arange(max_lag - 1, -1, -1): + trans = transforms.blended_transform_factory( + ax.transData, fig.transFigure) + if tau == max_lag - 1: + ax.text(pos[tau][0], 1.-label_space_top, r'$t$', + fontsize=label_fontsize, + horizontalalignment='center', + verticalalignment='top', transform=trans) + else: + ax.text(pos[tau][0], 1.-label_space_top, + r'$t-%s$' % str(max_lag - tau - 1), + fontsize=label_fontsize, + horizontalalignment='center', verticalalignment='top', + transform=trans) + + # fig.subplots_adjust(left=0.1, right=.98, bottom=.25, top=.9) + # savestring = os.path.expanduser(save_name) + if save_name is not None: + pyplot.savefig(save_name) + else: + pyplot.show()
+ +
[docs]def plot_mediation_time_series_graph( + path_node_array, + tsg_path_val_matrix, + var_names=None, + fig_ax=None, + figsize=None, + link_colorbar_label='link coeff. (edge color)', + node_colorbar_label='MCE (node color)', + save_name=None, + link_width=None, + arrow_linewidth=20., + vmin_edges=-1, + vmax_edges=1., + edge_ticks=.4, + cmap_edges='RdBu_r', + order=None, + vmin_nodes=-1., + vmax_nodes=1., + node_ticks=.4, + cmap_nodes='RdBu_r', + node_size=10, + arrowhead_size=20, + curved_radius=.2, + label_fontsize=10, + alpha=1., + node_label_size=10, + label_space_left=0.1, + label_space_top=0., + network_lower_bound=0.2 + ): + """Creates a mediation time series graph plot. + + This is still in beta. The time series graph's links are colored by + val_matrix. + + Parameters + ---------- + tsg_path_val_matrix : array_like + Matrix of shape (N*tau_max, N*tau_max) containing link weight values. + + path_node_array: array_like + Array of shape (N,) containing node values. + + var_names : list, optional (default: None) + List of variable names. If None, range(N) is used. + + fig_ax : tuple of figure and axis object, optional (default: None) + Figure and axes instance. If None they are created. + + figsize : tuple + Size of figure. + + save_name : str, optional (default: None) + Name of figure file to save figure. If None, figure is shown in window. + + link_colorbar_label : str, optional (default: 'link coeff. (edge color)') + Link colorbar label. + + node_colorbar_label : str, optional (default: 'MCE (node color)') + Node colorbar label. + + link_width : array-like, optional (default: None) + Array of val_matrix.shape specifying relative link width with maximum + given by arrow_linewidth. If None, all links have same width. + + order : list, optional (default: None) + order of variables from top to bottom. + + arrow_linewidth : float, optional (default: 30) + Linewidth. + + vmin_edges : float, optional (default: -1) + Link colorbar scale lower bound. + + vmax_edges : float, optional (default: 1) + Link colorbar scale upper bound. + + edge_ticks : float, optional (default: 0.4) + Link tick mark interval. + + cmap_edges : str, optional (default: 'RdBu_r') + Colormap for links. + + vmin_nodes : float, optional (default: 0) + Node colorbar scale lower bound. + + vmax_nodes : float, optional (default: 1) + Node colorbar scale upper bound. + + node_ticks : float, optional (default: 0.4) + Node tick mark interval. + + cmap_nodes : str, optional (default: 'OrRd') + Colormap for links. + + node_size : int, optional (default: 20) + Node size. + + arrowhead_size : int, optional (default: 20) + Size of link arrow head. Passed on to FancyArrowPatch object. + + curved_radius, float, optional (default: 0.2) + Curvature of links. Passed on to FancyArrowPatch object. + + label_fontsize : int, optional (default: 10) + Fontsize of colorbar labels. + + alpha : float, optional (default: 1.) + Opacity. + + node_label_size : int, optional (default: 10) + Fontsize of node labels. + + link_label_fontsize : int, optional (default: 6) + Fontsize of link labels. + + label_space_left : float, optional (default: 0.1) + Fraction of horizontal figure space to allocate left of plot for labels. + + label_space_top : float, optional (default: 0.) + Fraction of vertical figure space to allocate top of plot for labels. + + network_lower_bound : float, optional (default: 0.2) + Fraction of vertical space below graph plot. + """ + + import networkx + + N = len(path_node_array) + Nmaxlag = tsg_path_val_matrix.shape[0] + max_lag = Nmaxlag // N + + if var_names is None: + var_names = range(N) + + if fig_ax is None: + fig = pyplot.figure(figsize=figsize) + ax = fig.add_subplot(111, frame_on=False) + else: + fig, ax = fig_ax + + if link_width is not None and not np.all(link_width >= 0.): + raise ValueError("link_width must be non-negative") + + if order is None: + order = range(N) + + if set(order) != set(range(N)): + raise ValueError("order must be a permutation of range(N)") + + def translate(row, lag): + return row * max_lag + lag + + # Define graph links by absolute maximum (positive or negative like for + # partial correlation) + tsg = tsg_path_val_matrix + tsg_attr = np.zeros((N * max_lag, N * max_lag)) + + G = networkx.DiGraph(tsg) + + # node_color = np.zeros(N) + # list of all strengths for color map + all_strengths = [] + # Add attributes, contemporaneous and directed links are handled separately + for (u, v, dic) in G.edges(data=True): + + dic['directed_attribute'] = None + + if u != v: + + if u % max_lag == v % max_lag: + dic['undirected'] = True + dic['directed'] = False + else: + dic['undirected'] = False + dic['directed'] = True + + dic['undirected_alpha'] = alpha + dic['undirected_color'] = _get_absmax( + np.array([[[tsg[u, v], + tsg[v, u]]]]) + ).squeeze() + dic['undirected_width'] = arrow_linewidth + all_strengths.append(dic['undirected_color']) + + dic['directed_alpha'] = alpha + + dic['directed_width'] = arrow_linewidth + + # value at argmax of average + dic['directed_color'] = tsg[u, v] + all_strengths.append(dic['directed_color']) + dic['label'] = None + + dic['directed_edge'] = False + dic['directed_edgecolor'] = None + dic['undirected_edge'] = False + dic['undirected_edgecolor'] = None + + # If no links are present, set value to zero + if len(all_strengths) == 0: + all_strengths = [0.] + + posarray = np.zeros((N * max_lag, 2)) + for i in range(N * max_lag): + + posarray[i] = np.array([(i % max_lag), (1. - i // max_lag)]) + + pos_tmp = {} + for i in range(N * max_lag): + # for n in range(N): + # for tau in range(max_lag): + # i = n*N + tau + pos_tmp[i] = np.array([((i % max_lag) - posarray.min(axis=0)[0]) / + (posarray.max(axis=0)[0] - + posarray.min(axis=0)[0]), + ((1. - i // max_lag) - + posarray.min(axis=0)[1]) / + (posarray.max(axis=0)[1] - + posarray.min(axis=0)[1])]) +# print pos[i] + pos = {} + for n in range(N): + for tau in range(max_lag): + pos[n * max_lag + tau] = pos_tmp[order[n] * max_lag + tau] + + node_color = np.zeros(N * max_lag) + for inet, n in enumerate(range(0, N * max_lag, max_lag)): + node_color[n:n+max_lag] = path_node_array[inet] + + # node_rings = {0: {'sizes': None, 'color_array': color_array, + # 'label': '', 'colorbar': False, + # } + # } + + node_rings = {0: {'sizes': None, 'color_array': node_color, + 'cmap': cmap_nodes, 'vmin': vmin_nodes, + 'vmax': vmax_nodes, 'ticks': node_ticks, + 'label': node_colorbar_label, 'colorbar': True, + } + } + + # ] for v in range(max_lag)] + node_labels = ['' for i in range(N * max_lag)] + + _draw_network_with_curved_edges( + fig=fig, ax=ax, + G=deepcopy(G), pos=pos, + # dictionary of rings: {0:{'sizes':(N,)-array, 'color_array':(N,)-array + # or None, 'cmap':string, + node_rings=node_rings, + # 'vmin':float or None, 'vmax':float or None, 'label':string or None}} + node_labels=node_labels, node_label_size=node_label_size, + node_alpha=alpha, standard_size=node_size, + standard_cmap='OrRd', standard_color='grey', + log_sizes=False, + cmap_links=cmap_edges, links_vmin=vmin_edges, + links_vmax=vmax_edges, links_ticks=edge_ticks, + + cmap_links_edges='YlOrRd', links_edges_vmin=-1., links_edges_vmax=1., + links_edges_ticks=.2, link_edge_colorbar_label='link_edge', + + arrowstyle='simple', arrowhead_size=arrowhead_size, + curved_radius=curved_radius, label_fontsize=label_fontsize, + label_fraction=.5, + link_colorbar_label=link_colorbar_label, undirected_curved=True, + network_lower_bound=network_lower_bound + # undirected_style=undirected_style + ) + + for i in range(N): + trans = transforms.blended_transform_factory( + fig.transFigure, ax.transData) + ax.text(label_space_left, pos[order[i] * max_lag][1], + '%s' % str(var_names[order[i]]), fontsize=label_fontsize, + horizontalalignment='left', verticalalignment='center', + transform=trans) + + for tau in np.arange(max_lag - 1, -1, -1): + trans = transforms.blended_transform_factory( + ax.transData, fig.transFigure) + if tau == max_lag - 1: + ax.text(pos[tau][0], 1.-label_space_top, r'$t$', + fontsize=label_fontsize, + horizontalalignment='center', + verticalalignment='top', transform=trans) + else: + ax.text(pos[tau][0], 1.-label_space_top, + r'$t-%s$' % str(max_lag - tau - 1), + fontsize=label_fontsize, + horizontalalignment='center', verticalalignment='top', + transform=trans) + + # fig.subplots_adjust(left=0.1, right=.98, bottom=.25, top=.9) + # savestring = os.path.expanduser(save_name) + if save_name is not None: + pyplot.savefig(save_name) + else: + pyplot.show()
+ +
[docs]def plot_mediation_graph( + path_val_matrix, + path_node_array=None, + var_names=None, + fig_ax=None, + figsize=None, + save_name=None, + link_colorbar_label='link coeff. (edge color)', + node_colorbar_label='MCE (node color)', + link_width=None, + node_pos=None, + arrow_linewidth=30., + vmin_edges=-1, + vmax_edges=1., + edge_ticks=.4, + cmap_edges='RdBu_r', + vmin_nodes=-1., + vmax_nodes=1., + node_ticks=.4, + cmap_nodes='RdBu_r', + node_size=20, + arrowhead_size=20, + curved_radius=.2, + label_fontsize=10, + lag_array=None, + alpha=1., + node_label_size=10, + link_label_fontsize=6, + network_lower_bound=0.2, + ): + """Creates a network plot visualizing the pathways of a mediation analysis. + + This is still in beta. The network is defined from non-zero entries in + ``path_val_matrix``. Nodes denote variables, straight links contemporaneous + dependencies and curved arrows lagged dependencies. The node color denotes + the mediated causal effect (MCE) and the link color the value at the lag + with maximal link coefficient. The link label lists the lags with + significant dependency in order of absolute magnitude. The network can also + be plotted over a map drawn before on the same axis. Then the node positions + can be supplied in appropriate axis coordinates via node_pos. + + Parameters + ---------- + path_val_matrix : array_like + Matrix of shape (N, N, tau_max+1) containing link weight values. + + path_node_array: array_like + Array of shape (N,) containing node values. + + var_names : list, optional (default: None) + List of variable names. If None, range(N) is used. + + fig_ax : tuple of figure and axis object, optional (default: None) + Figure and axes instance. If None they are created. + + figsize : tuple + Size of figure. + + save_name : str, optional (default: None) + Name of figure file to save figure. If None, figure is shown in window. + + link_colorbar_label : str, optional (default: 'link coeff. (edge color)') + Link colorbar label. + + node_colorbar_label : str, optional (default: 'MCE (node color)') + Node colorbar label. + + link_width : array-like, optional (default: None) + Array of val_matrix.shape specifying relative link width with maximum + given by arrow_linewidth. If None, all links have same width. + + node_pos : dictionary, optional (default: None) + Dictionary of node positions in axis coordinates of form + node_pos = {'x':array of shape (N,), 'y':array of shape(N)}. These + coordinates could have been transformed before for basemap plots. + + arrow_linewidth : float, optional (default: 30) + Linewidth. + + vmin_edges : float, optional (default: -1) + Link colorbar scale lower bound. + + vmax_edges : float, optional (default: 1) + Link colorbar scale upper bound. + + edge_ticks : float, optional (default: 0.4) + Link tick mark interval. + + cmap_edges : str, optional (default: 'RdBu_r') + Colormap for links. + + vmin_nodes : float, optional (default: 0) + Node colorbar scale lower bound. + + vmax_nodes : float, optional (default: 1) + Node colorbar scale upper bound. + + node_ticks : float, optional (default: 0.4) + Node tick mark interval. + + cmap_nodes : str, optional (default: 'OrRd') + Colormap for links. + + node_size : int, optional (default: 20) + Node size. + + arrowhead_size : int, optional (default: 20) + Size of link arrow head. Passed on to FancyArrowPatch object. + + curved_radius, float, optional (default: 0.2) + Curvature of links. Passed on to FancyArrowPatch object. + + label_fontsize : int, optional (default: 10) + Fontsize of colorbar labels. + + alpha : float, optional (default: 1.) + Opacity. + + node_label_size : int, optional (default: 10) + Fontsize of node labels. + + link_label_fontsize : int, optional (default: 6) + Fontsize of link labels. + + network_lower_bound : float, optional (default: 0.2) + Fraction of vertical space below graph plot. + + lag_array : array, optional (default: None) + Optional specification of lags overwriting np.arange(0, tau_max+1) + """ + import networkx + + val_matrix = path_val_matrix + + if fig_ax is None: + fig = pyplot.figure(figsize=figsize) + ax = fig.add_subplot(111, frame_on=False) + else: + fig, ax = fig_ax + + if link_width is not None and not np.all(link_width >= 0.): + raise ValueError("link_width must be non-negative") + + N, N, dummy = val_matrix.shape + tau_max = dummy - 1 + + if var_names is None: + var_names = range(N) + + # Define graph links by absolute maximum (positive or negative like for + # partial correlation) + # val_matrix[np.abs(val_matrix) < sig_thres] = 0. + link_matrix = val_matrix != 0. + net = _get_absmax(val_matrix) + G = networkx.DiGraph(net) + + node_color = np.zeros(N) + # list of all strengths for color map + all_strengths = [] + # Add attributes, contemporaneous and directed links are handled separately + for (u, v, dic) in G.edges(data=True): + dic['directed_attribute'] = None + + # average lagfunc for link u --> v ANDOR u -- v + if tau_max > 0: + # argmax of absolute maximum + argmax = np.abs(val_matrix[u, v][1:]).argmax() + 1 + else: + argmax = 0 + if u != v: + # For contemp links masking or finite samples can lead to different + # values for u--v and v--u + # Here we use the maximum for the width and weight (=color) + # of the link + # Draw link if u--v OR v--u at lag 0 is nonzero + # dic['undirected'] = ((np.abs(val_matrix[u, v][0]) >= + # sig_thres[u, v][0]) or + # (np.abs(val_matrix[v, u][0]) >= + # sig_thres[v, u][0])) + dic['undirected'] = (link_matrix[u,v,0] or link_matrix[v,u,0]) + dic['undirected_alpha'] = alpha + # value at argmax of average + if np.abs(val_matrix[u, v][0] - val_matrix[v, u][0]) > .0001: + print("Contemporaneous I(%d; %d)=%.3f != I(%d; %d)=%.3f" % ( + u, v, val_matrix[u, v][0], v, u, val_matrix[v, u][0]) + + " due to conditions, finite sample effects or " + "masking, here edge color = " + "larger (absolute) value.") + dic['undirected_color'] = _get_absmax( + np.array([[[val_matrix[u, v][0], + val_matrix[v, u][0]]]])).squeeze() + if link_width is None: + dic['undirected_width'] = arrow_linewidth + else: + dic['undirected_width'] = link_width[ + u, v, 0] / link_width.max() * arrow_linewidth + + all_strengths.append(dic['undirected_color']) + + if tau_max > 0: + # True if ensemble mean at lags > 0 is nonzero + # dic['directed'] = np.any( + # np.abs(val_matrix[u, v][1:]) >= sig_thres[u, v][1:]) + dic['directed'] = np.any(link_matrix[u,v,1:]) + else: + dic['directed'] = False + dic['directed_alpha'] = alpha + if link_width is None: + # fraction of nonzero values + dic['directed_width'] = arrow_linewidth + else: + dic['directed_width'] = link_width[ + u, v, argmax] / link_width.max() * arrow_linewidth + + # value at argmax of average + dic['directed_color'] = val_matrix[u, v][argmax] + all_strengths.append(dic['directed_color']) + + # Sorted list of significant lags (only if robust wrt + # d['min_ensemble_frac']) + if tau_max > 0: + lags = np.abs(val_matrix[u, v][1:]).argsort()[::-1] + 1 + sig_lags = (np.where(link_matrix[u, v,1:])[0] + 1).tolist() + else: + lags, sig_lags = [], [] + if lag_array is not None: + dic['label'] = str([lag_array[l] for l in lags if l in sig_lags])[1:-1] + else: + dic['label'] = str([l for l in lags if l in sig_lags])[1:-1] + else: + # Node color is max of average autodependency + node_color[u] = val_matrix[u, v][argmax] + + dic['directed_edge'] = False + dic['directed_edgecolor'] = None + dic['undirected_edge'] = False + dic['undirected_edgecolor'] = None + + node_color = path_node_array + # print node_color + # If no links are present, set value to zero + if len(all_strengths) == 0: + all_strengths = [0.] + + if node_pos is None: + pos = networkx.circular_layout(deepcopy(G)) +# pos = networkx.spring_layout(deepcopy(G)) + else: + pos = {} + for i in range(N): + pos[i] = (node_pos['x'][i], node_pos['y'][i]) + + node_rings = {0: {'sizes': None, 'color_array': node_color, + 'cmap': cmap_nodes, 'vmin': vmin_nodes, + 'vmax': vmax_nodes, 'ticks': node_ticks, + 'label': node_colorbar_label, 'colorbar': True, + } + } + + _draw_network_with_curved_edges( + fig=fig, ax=ax, + G=deepcopy(G), pos=pos, + # dictionary of rings: {0:{'sizes':(N,)-array, 'color_array':(N,)-array + # or None, 'cmap':string, + node_rings=node_rings, + # 'vmin':float or None, 'vmax':float or None, 'label':string or None}} + node_labels=var_names, node_label_size=node_label_size, + node_alpha=alpha, standard_size=node_size, + standard_cmap='OrRd', standard_color='orange', + log_sizes=False, + cmap_links=cmap_edges, links_vmin=vmin_edges, + links_vmax=vmax_edges, links_ticks=edge_ticks, + + cmap_links_edges='YlOrRd', links_edges_vmin=-1., links_edges_vmax=1., + links_edges_ticks=.2, link_edge_colorbar_label='link_edge', + + arrowstyle='simple', arrowhead_size=arrowhead_size, + curved_radius=curved_radius, label_fontsize=label_fontsize, + link_label_fontsize=link_label_fontsize, + link_colorbar_label=link_colorbar_label, + network_lower_bound=network_lower_bound, + # label_fraction=label_fraction, + # undirected_style=undirected_style + ) + + # fig.subplots_adjust(left=0.1, right=.9, bottom=.25, top=.95) + # savestring = os.path.expanduser(save_name) + if save_name is not None: + pyplot.savefig(save_name) + else: + pyplot.show()
+ +if __name__ == '__main__': + + + from tigramite.independence_tests import ParCorr + import tigramite.data_processing as pp + np.random.seed(42) + val_matrix = np.random.rand(3,3,4) + link_matrix = np.abs(val_matrix) > .7 + # print link_matrix + data = np.random.randn(100,3) + mask = np.random.randint(0, 2, size=(100,3)) + dataframe = pp.DataFrame(data, mask=mask) + + + # data = np.random.randn(100, 3) + # datatime = np.arange(100) + # mask = np.zeros(data.shape) + + # mask[:int(len(data)/2)]=True + + # data[:,0] = -99. + # plot_lagfuncs(val_matrix=val_matrix, + # setup_args={'figsize':(10,10), + # 'label_space_top':0.05, + # 'label_space_left':0.1, + # 'x_base':1, 'y_base':5, + # 'var_names':range(3), + # 'lag_array':np.array(['a%d' % i for i in range(4)])}, + # name='test.pdf', + # ) + + + # plot_timeseries( + # dataframe=dataframe, + # save_name='/home/rung_ja/Downloads/test.pdf', + # fig_axes=None, + # var_units=None, + # time_label='years', + # use_mask=True, + # grey_masked_samples='data', + # data_linewidth=1., + # skip_ticks_data_x=1, + # skip_ticks_data_y=1, + # label_fontsize=8, + # figsize=(3.375, 3.), + # ) + + # lagmat = setup_matrix(3, 3, range(3), lag_units = 'months') + + # lagmat.add_lagfuncs( + # val_matrix=val_matrix, + # # sig_thres=None, + # # link_matrix=link_matrix + # ) + # lagmat.savefig() + + # fig = pyplot.figure(figsize=(4, 3), frameon=False) + # ax = fig.add_subplot(111, frame_on=False) + + plot_graph( + figsize=(3, 3), + val_matrix=val_matrix, + sig_thres=None, + link_matrix=link_matrix, + var_names=range(len(val_matrix)), + save_name='/home/rung_ja/Downloads/test.pdf', + ) + + + # plot_time_series_graph( + # val_matrix=val_matrix, + # sig_thres=None, + # link_matrix=link_matrix, + # var_names=range(len(val_matrix)), + # undirected_style='dashed', + # ) + # pyplot.show() +
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt new file mode 100644 index 00000000..f42bfb43 --- /dev/null +++ b/docs/_build/html/_sources/index.rst.txt @@ -0,0 +1,123 @@ +.. Tigramite documentation master file, created by + sphinx-quickstart on Thu May 11 18:32:05 2017. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +TIGRAMITE +========= + +`Github repo `_ + +Tigramite is a causal time series analysis python package. It allows to efficiently reconstruct causal graphs from high-dimensional time series datasets and model the obtained causal dependencies for causal mediation and prediction analyses. Causal discovery is based on linear as well as non-parametric conditional independence tests applicable to discrete or continuously-valued time series. Also includes functions for high-quality plots of the results. Please cite the following papers depending on which method you use: + + +0. J. Runge et al. (2019): Inferring causation from time series in Earth system sciences. + Nature Communications, 10(1):2553. + https://www.nature.com/articles/s41467-019-10105-3 + +1. J. Runge, P. Nowack, M. Kretschmer, S. Flaxman, D. Sejdinovic (2019): Detecting and quantifying causal associations in large nonlinear time series datasets. + Sci. Adv. 5, eaau4996. + https://advances.sciencemag.org/content/5/11/eaau4996 + +2. J. Runge et al. (2015): Identifying causal gateways and mediators in complex spatio-temporal systems. + Nature Communications, 6, 8502. + http://doi.org/10.1038/ncomms9502 + +3. J. Runge (2015): Quantifying information transfer and mediation along causal pathways in complex systems. + Phys. Rev. E, 92(6), 62829. + http://doi.org/10.1103/PhysRevE.92.062829 + +4. J. Runge (2018): Conditional Independence Testing Based on a Nearest-Neighbor Estimator of Conditional Mutual Information. + In Proceedings of the 21st International Conference on Artificial Intelligence and Statistics. + http://proceedings.mlr.press/v84/runge18a.html + +5. J. Runge (2018): Causal Network Reconstruction from Time Series: From Theoretical Assumptions to Practical Estimation. + Chaos: An Interdisciplinary Journal of Nonlinear Science 28 (7): 075310. + https://aip.scitation.org/doi/10.1063/1.5025050 + + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + +.. autosummary:: + + tigramite.pcmci.PCMCI + tigramite.independence_tests.CondIndTest + tigramite.independence_tests.ParCorr + tigramite.independence_tests.GPDC + tigramite.independence_tests.CMIknn + tigramite.independence_tests.CMIsymb + tigramite.independence_tests.RCOT + tigramite.data_processing + tigramite.models.Models + tigramite.models.LinearMediation + tigramite.models.Prediction + tigramite.plotting + + +:mod:`tigramite.pcmci`: PCMCI +=========================================== + +.. autoclass:: tigramite.pcmci.PCMCI + :members: + +:mod:`tigramite.independence_tests`: Conditional independence tests +================================================================================= + +Base class: + +.. autoclass:: tigramite.independence_tests.CondIndTest + :members: + +Test statistics: + +.. autoclass:: tigramite.independence_tests.ParCorr + :members: + +.. autoclass:: tigramite.independence_tests.GPDC + :members: + +.. autoclass:: tigramite.independence_tests.CMIknn + :members: + +.. autoclass:: tigramite.independence_tests.CMIsymb + :members: + +.. autoclass:: tigramite.independence_tests.RCOT + :members: + +:mod:`tigramite.data_processing`: Data processing functions +=========================================================== + +.. automodule:: tigramite.data_processing + :members: + +:mod:`tigramite.models`: Time series modeling, mediation, and prediction +======================================================================== + +Base class: + +.. autoclass:: tigramite.models.Models + :members: + +Derived classes: + +.. autoclass:: tigramite.models.LinearMediation + :members: + +.. autoclass:: tigramite.models.Prediction + :members: + +:mod:`tigramite.plotting`: Plotting functions +============================================= + +.. automodule:: tigramite.plotting + :members: + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_build/html/_static/ajax-loader.gif b/docs/_build/html/_static/ajax-loader.gif new file mode 100644 index 00000000..61faf8ca Binary files /dev/null and b/docs/_build/html/_static/ajax-loader.gif differ diff --git a/docs/_build/html/_static/alabaster.css b/docs/_build/html/_static/alabaster.css new file mode 100644 index 00000000..bc420a48 --- /dev/null +++ b/docs/_build/html/_static/alabaster.css @@ -0,0 +1,593 @@ + + + + + + + + + + + + + + + + + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; + font-size: 17px; + background-color: white; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + width: 940px; + margin: 30px auto 0 auto; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 220px; +} + +div.sphinxsidebar { + width: 220px; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 0 30px; +} + +div.body > .section { + text-align: left; +} + +div.footer { + width: 940px; + margin: 20px auto 30px auto; + font-size: 14px; + color: #888; + text-align: right; +} + +div.footer a { + color: #888; +} + + +div.relations { + display: none; +} + + +div.sphinxsidebar a { + color: #444; + text-decoration: none; + border-bottom: 1px dotted #999; +} + +div.sphinxsidebar a:hover { + border-bottom: 1px solid #999; +} + +div.sphinxsidebar { + font-size: 14px; + line-height: 1.5; +} + +div.sphinxsidebarwrapper { + padding: 18px 10px; +} + +div.sphinxsidebarwrapper p.logo { + padding: 0; + margin: -10px 0 0 0px; + text-align: center; +} + +div.sphinxsidebarwrapper h1.logo { + margin-top: -10px; + text-align: center; + margin-bottom: 5px; + text-align: left; +} + +div.sphinxsidebarwrapper h1.logo-name { + margin-top: 0px; +} + +div.sphinxsidebarwrapper p.blurb { + margin-top: 0; + font-style: normal; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: 'Garamond', 'Georgia', serif; + color: #444; + font-size: 24px; + font-weight: normal; + margin: 0 0 5px 0; + padding: 0; +} + +div.sphinxsidebar h4 { + font-size: 20px; +} + +div.sphinxsidebar h3 a { + color: #444; +} + +div.sphinxsidebar p.logo a, +div.sphinxsidebar h3 a, +div.sphinxsidebar p.logo a:hover, +div.sphinxsidebar h3 a:hover { + border: none; +} + +div.sphinxsidebar p { + color: #555; + margin: 10px 0; +} + +div.sphinxsidebar ul { + margin: 10px 0; + padding: 0; + color: #000; +} + +div.sphinxsidebar ul li.toctree-l1 > a { + font-size: 120%; +} + +div.sphinxsidebar ul li.toctree-l2 > a { + font-size: 110%; +} + +div.sphinxsidebar input { + border: 1px solid #CCC; + font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; + font-size: 1em; +} + +div.sphinxsidebar hr { + border: none; + height: 1px; + color: #AAA; + background: #AAA; + + text-align: left; + margin-left: 0; + width: 50%; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #004B6B; + text-decoration: underline; +} + +a:hover { + color: #6D4100; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Garamond', 'Georgia', serif; + font-weight: normal; + margin: 30px 0px 10px 0px; + padding: 0; +} + +div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } +div.body h2 { font-size: 180%; } +div.body h3 { font-size: 150%; } +div.body h4 { font-size: 130%; } +div.body h5 { font-size: 100%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #DDD; + padding: 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + color: #444; + background: #EAEAEA; +} + +div.body p, div.body dd, div.body li { + line-height: 1.4em; +} + +div.admonition { + margin: 20px 0px; + padding: 10px 30px; + background-color: #FCC; + border: 1px solid #FAA; +} + +div.admonition tt.xref, div.admonition a tt { + border-bottom: 1px solid #fafafa; +} + +dd div.admonition { + margin-left: -60px; + padding-left: 60px; +} + +div.admonition p.admonition-title { + font-family: 'Garamond', 'Georgia', serif; + font-weight: normal; + font-size: 24px; + margin: 0 0 10px 0; + padding: 0; + line-height: 1; +} + +div.admonition p.last { + margin-bottom: 0; +} + +div.highlight { + background-color: white; +} + +dt:target, .highlight { + background: #FAF3E8; +} + +div.note { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.seealso { + background-color: #EEE; + border: 1px solid #CCC; +} + +div.topic { + background-color: #eee; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre, tt, code { + font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; + font-size: 0.9em; +} + +.hll { + background-color: #FFC; + margin: 0 -12px; + padding: 0 12px; + display: block; +} + +img.screenshot { +} + +tt.descname, tt.descclassname, code.descname, code.descclassname { + font-size: 0.95em; +} + +tt.descname, code.descname { + padding-right: 0.08em; +} + +img.screenshot { + -moz-box-shadow: 2px 2px 4px #eee; + -webkit-box-shadow: 2px 2px 4px #eee; + box-shadow: 2px 2px 4px #eee; +} + +table.docutils { + border: 1px solid #888; + -moz-box-shadow: 2px 2px 4px #eee; + -webkit-box-shadow: 2px 2px 4px #eee; + box-shadow: 2px 2px 4px #eee; +} + +table.docutils td, table.docutils th { + border: 1px solid #888; + padding: 0.25em 0.7em; +} + +table.field-list, table.footnote { + border: none; + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} + +table.footnote { + margin: 15px 0; + width: 100%; + border: 1px solid #EEE; + background: #FDFDFD; + font-size: 0.9em; +} + +table.footnote + table.footnote { + margin-top: -15px; + border-top: none; +} + +table.field-list th { + padding: 0 0.8em 0 0; +} + +table.field-list td { + padding: 0; +} + +table.field-list p { + margin-bottom: 0.8em; +} + +table.footnote td.label { + width: 0px; + padding: 0.3em 0 0.3em 0.5em; +} + +table.footnote td { + padding: 0.3em 0.5em; +} + +dl { + margin: 0; + padding: 0; +} + +dl dd { + margin-left: 30px; +} + +blockquote { + margin: 0 0 0 30px; + padding: 0; +} + +ul, ol { + margin: 10px 0 10px 30px; + padding: 0; +} + +pre { + background: #EEE; + padding: 7px 30px; + margin: 15px 0px; + line-height: 1.3em; +} + +dl pre, blockquote pre, li pre { + margin-left: 0; + padding-left: 30px; +} + +dl dl pre { + margin-left: -90px; + padding-left: 90px; +} + +tt, code { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ +} + +tt.xref, code.xref, a tt { + background-color: #FBFBFB; + border-bottom: 1px solid white; +} + +a.reference { + text-decoration: none; + border-bottom: 1px dotted #004B6B; +} + +a.reference:hover { + border-bottom: 1px solid #6D4100; +} + +a.footnote-reference { + text-decoration: none; + font-size: 0.7em; + vertical-align: top; + border-bottom: 1px dotted #004B6B; +} + +a.footnote-reference:hover { + border-bottom: 1px solid #6D4100; +} + +a:hover tt, a:hover code { + background: #EEE; +} + + +@media screen and (max-width: 870px) { + + div.sphinxsidebar { + display: none; + } + + div.document { + width: 100%; + + } + + div.documentwrapper { + margin-left: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + } + + div.bodywrapper { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + } + + ul { + margin-left: 0; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .bodywrapper { + margin: 0; + } + + .footer { + width: auto; + } + + .github { + display: none; + } + + + +} + + + +@media screen and (max-width: 875px) { + + body { + margin: 0; + padding: 20px 30px; + } + + div.documentwrapper { + float: none; + background: white; + } + + div.sphinxsidebar { + display: block; + float: none; + width: 102.5%; + margin: 50px -30px -20px -30px; + padding: 10px 20px; + background: #333; + color: #FFF; + } + + div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, + div.sphinxsidebar h3 a { + color: white; + } + + div.sphinxsidebar a { + color: #AAA; + } + + div.sphinxsidebar p.logo { + display: none; + } + + div.document { + width: 100%; + margin: 0; + } + + div.footer { + display: none; + } + + div.bodywrapper { + margin: 0; + } + + div.body { + min-height: 0; + padding: 0; + } + + .rtd_doc_footer { + display: none; + } + + .document { + width: auto; + } + + .footer { + width: auto; + } + + .footer { + width: auto; + } + + .github { + display: none; + } +} + + +/* misc. */ + +.revsys-inline { + display: none!important; +} + +/* Make nested-list/multi-paragraph items look better in Releases changelog + * pages. Without this, docutils' magical list fuckery causes inconsistent + * formatting between different release sub-lists. + */ +div#changelog > div.section > ul > li > p:only-child { + margin-bottom: 0; +} + +/* Hide fugly table cell borders in ..bibliography:: directive output */ +table.docutils.citation, table.docutils.citation td, table.docutils.citation th { + border: none; + /* Below needed in some edge cases; if not applied, bottom shadows appear */ + -moz-box-shadow: none; + -webkit-box-shadow: none; + box-shadow: none; +} \ No newline at end of file diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css new file mode 100644 index 00000000..dc88b5a2 --- /dev/null +++ b/docs/_build/html/_static/basic.css @@ -0,0 +1,632 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox input[type="text"] { + width: 170px; +} + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlighted { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +div.code-block-caption { + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +div.code-block-caption + div > div.highlight > pre { + margin-top: 0; +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + padding: 1em 1em 0; +} + +div.literal-block-wrapper div.highlight { + margin: 0; +} + +code.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +code.descclassname { + background-color: transparent; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: relative; + left: 0px; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_build/html/_static/comment-bright.png b/docs/_build/html/_static/comment-bright.png new file mode 100644 index 00000000..15e27edb Binary files /dev/null and b/docs/_build/html/_static/comment-bright.png differ diff --git a/docs/_build/html/_static/comment-close.png b/docs/_build/html/_static/comment-close.png new file mode 100644 index 00000000..4d91bcf5 Binary files /dev/null and b/docs/_build/html/_static/comment-close.png differ diff --git a/docs/_build/html/_static/comment.png b/docs/_build/html/_static/comment.png new file mode 100644 index 00000000..dfbc0cbd Binary files /dev/null and b/docs/_build/html/_static/comment.png differ diff --git a/docs/_build/html/_static/contents.png b/docs/_build/html/_static/contents.png new file mode 100644 index 00000000..6c59aa1f Binary files /dev/null and b/docs/_build/html/_static/contents.png differ diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js new file mode 100644 index 00000000..56549772 --- /dev/null +++ b/docs/_build/html/_static/doctools.js @@ -0,0 +1,287 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this); + }); + } + } + return this.each(function() { + highlight(this); + }); +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + $(document).keyup(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box or textarea + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { + switch (event.keyCode) { + case 37: // left + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + case 39: // right + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); \ No newline at end of file diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js new file mode 100644 index 00000000..8a5f4b08 --- /dev/null +++ b/docs/_build/html/_static/documentation_options.js @@ -0,0 +1,9 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: '', + VERSION: '4.0', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + FILE_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt' +}; \ No newline at end of file diff --git a/docs/_build/html/_static/down-pressed.png b/docs/_build/html/_static/down-pressed.png new file mode 100644 index 00000000..5756c8ca Binary files /dev/null and b/docs/_build/html/_static/down-pressed.png differ diff --git a/docs/_build/html/_static/down.png b/docs/_build/html/_static/down.png new file mode 100644 index 00000000..1b3bdad2 Binary files /dev/null and b/docs/_build/html/_static/down.png differ diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png new file mode 100644 index 00000000..a858a410 Binary files /dev/null and b/docs/_build/html/_static/file.png differ diff --git a/docs/_build/html/_static/jquery-3.1.0.js b/docs/_build/html/_static/jquery-3.1.0.js new file mode 100644 index 00000000..f2fc2747 --- /dev/null +++ b/docs/_build/html/_static/jquery-3.1.0.js @@ -0,0 +1,10074 @@ +/*eslint-disable no-unused-vars*/ +/*! + * jQuery JavaScript Library v3.1.0 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2016-07-07T21:44Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var document = window.document; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var concat = arr.concat; + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + + + + function DOMEval( code, doc ) { + doc = doc || document; + + var script = doc.createElement( "script" ); + + script.text = code; + doc.head.appendChild( script ).parentNode.removeChild( script ); + } +/* global Symbol */ +// Defining this global in .eslintrc would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.1.0", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }, + + // Support: Android <=4.0 only + // Make sure we trim BOM and NBSP + rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, + + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g, + + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function( all, letter ) { + return letter.toUpperCase(); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num != null ? + + // Return just the one element from the set + ( num < 0 ? this[ num + this.length ] : this[ num ] ) : + + // Return all the elements in a clean array + slice.call( this ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = jQuery.isArray( copy ) ) ) ) { + + if ( copyIsArray ) { + copyIsArray = false; + clone = src && jQuery.isArray( src ) ? src : []; + + } else { + clone = src && jQuery.isPlainObject( src ) ? src : {}; + } + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isFunction: function( obj ) { + return jQuery.type( obj ) === "function"; + }, + + isArray: Array.isArray, + + isWindow: function( obj ) { + return obj != null && obj === obj.window; + }, + + isNumeric: function( obj ) { + + // As of jQuery 3.0, isNumeric is limited to + // strings and numbers (primitives or objects) + // that can be coerced to finite numbers (gh-2662) + var type = jQuery.type( obj ); + return ( type === "number" || type === "string" ) && + + // parseFloat NaNs numeric-cast false positives ("") + // ...but misinterprets leading-number strings, particularly hex literals ("0x...") + // subtraction forces infinities to NaN + !isNaN( obj - parseFloat( obj ) ); + }, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + + /* eslint-disable no-unused-vars */ + // See https://github.com/eslint/eslint/issues/6125 + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + type: function( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; + }, + + // Evaluates a script in a global context + globalEval: function( code ) { + DOMEval( code ); + }, + + // Convert dashed to camelCase; used by the css and data modules + // Support: IE <=9 - 11, Edge 12 - 13 + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // Support: Android <=4.0 only + trim: function( text ) { + return text == null ? + "" : + ( text + "" ).replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function( fn, context ) { + var tmp, args, proxy; + + if ( typeof context === "string" ) { + tmp = fn[ context ]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if ( !jQuery.isFunction( fn ) ) { + return undefined; + } + + // Simulated bind + args = slice.call( arguments, 2 ); + proxy = function() { + return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + now: Date.now, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = jQuery.type( obj ); + + if ( type === "function" || jQuery.isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.0 + * https://sizzlejs.com/ + * + * Copyright jQuery Foundation and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2016-01-04 + */ +(function( window ) { + +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ({}).hasOwnProperty, + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[i] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), + + rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), + funescape = function( _, escaped, escapedWhitespace ) { + var high = "0x" + escaped - 0x10000; + // NaN means non-codepoint + // Support: Firefox<24 + // Workaround erroneous numeric interpretation of +"0x" + return high !== high || escapedWhitespace ? + escaped : + high < 0 ? + // BMP codepoint + String.fromCharCode( high + 0x10000 ) : + // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + disabledAncestor = addCombinator( + function( elem ) { + return elem.disabled === true; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + (arr = slice.call( preferredDoc.childNodes )), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + push_native.apply( target, slice.call(els) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ( (target[j++] = els[i++]) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + + if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { + setDocument( context ); + } + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + + // ID selector + if ( (m = match[1]) ) { + + // Document context + if ( nodeType === 9 ) { + if ( (elem = context.getElementById( m )) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && (elem = newContext.getElementById( m )) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[2] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( (m = match[3]) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !compilerCache[ selector + " " ] && + (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { + + if ( nodeType !== 1 ) { + newContext = context; + newSelector = selector; + + // qSA looks outside Element context, which is not what we want + // Thanks to Andrew Dupont for this workaround technique + // Support: IE <=8 + // Exclude object elements + } else if ( context.nodeName.toLowerCase() !== "object" ) { + + // Capture the context ID, setting it first if necessary + if ( (nid = context.getAttribute( "id" )) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", (nid = expando) ); + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[i] = "#" + nid + " " + toSelector( groups[i] ); + } + newSelector = groups.join( "," ); + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + } + + if ( newSelector ) { + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return (cache[ key + " " ] = value); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement("fieldset"); + + try { + return !!fn( el ); + } catch (e) { + return false; + } finally { + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split("|"), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[i] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( (cur = cur.nextSibling) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return (name === "input" || name === "button") && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + // Known :disabled false positives: + // IE: *[disabled]:not(button, input, select, textarea, optgroup, option, menuitem, fieldset) + // not IE: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Check form elements and option elements for explicit disabling + return "label" in elem && elem.disabled === disabled || + "form" in elem && elem.disabled === disabled || + + // Check non-disabled form elements for fieldset[disabled] ancestors + "form" in elem && elem.disabled === false && ( + // Support: IE6-11+ + // Ancestry is covered for us + elem.isDisabled === disabled || + + // Otherwise, assume any non-