timeawarepc 1.2.4__tar.gz → 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {timeawarepc-1.2.4/timeawarepc.egg-info → timeawarepc-2.0.1}/PKG-INFO +32 -14
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/README.md +30 -12
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/setup.cfg +2 -2
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/setup.py +2 -2
- timeawarepc-2.0.1/test/test_optional_bootstrap.py +107 -0
- timeawarepc-2.0.1/test/test_partial_corr_shift_invariance.py +61 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/__init__.py +1 -1
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/find_cfc.py +15 -7
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/pcalg.py +13 -9
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/pcalg_helpers.py +13 -9
- timeawarepc-2.0.1/timeawarepc/tpc.py +194 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/tutorial.py +3 -3
- {timeawarepc-1.2.4 → timeawarepc-2.0.1/timeawarepc.egg-info}/PKG-INFO +32 -14
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc.egg-info/SOURCES.txt +2 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc.egg-info/requires.txt +1 -1
- timeawarepc-1.2.4/timeawarepc/tpc.py +0 -155
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/LICENSE +0 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/pyproject.toml +0 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/gc.py +0 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/simulate_data.py +0 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc/tpc_helpers.py +0 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc.egg-info/dependency_links.txt +0 -0
- {timeawarepc-1.2.4 → timeawarepc-2.0.1}/timeawarepc.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: timeawarepc
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: Time-Aware PC Python Package
|
|
5
5
|
Home-page: https://github.com/biswasr/TimeAwarePC
|
|
6
6
|
Author: Rahul Biswas
|
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: numpy
|
|
17
17
|
Requires-Dist: pandas
|
|
18
|
-
Requires-Dist: rpy2
|
|
18
|
+
Requires-Dist: rpy2>=3.5.11
|
|
19
19
|
Requires-Dist: networkx
|
|
20
20
|
Requires-Dist: scipy
|
|
21
21
|
Dynamic: author
|
|
@@ -39,17 +39,25 @@ Dynamic: summary
|
|
|
39
39
|
|
|
40
40
|
## Installation
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
### Recommended: conda environment (handles R + kpcalg automatically)
|
|
43
43
|
|
|
44
44
|
```
|
|
45
|
-
$
|
|
45
|
+
$ git clone https://github.com/shlizee/TimeAwarePC.git
|
|
46
|
+
$ cd TimeAwarePC
|
|
47
|
+
$ conda env create -f environment.yml
|
|
48
|
+
$ conda activate timeawarepc
|
|
49
|
+
$ Rscript install_r_deps.R # installs kpcalg from CRAN archive
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
This installs Python, R, rpy2, all required R packages (graph, RBGL, pcalg), and TimeAwarePC v2.0.0 in a single isolated environment.
|
|
53
|
+
|
|
54
|
+
### Manual install (alternative)
|
|
55
|
+
|
|
56
|
+
If you prefer to install without conda:
|
|
57
|
+
|
|
58
|
+
- Python >=3.9, <3.11
|
|
59
|
+
- R >= 4.0
|
|
60
|
+
- R package ```kpcalg``` and its dependencies, installed via R or RStudio:
|
|
53
61
|
```
|
|
54
62
|
> install.packages("BiocManager")
|
|
55
63
|
> BiocManager::install("graph")
|
|
@@ -57,10 +65,12 @@ $ pip install timeawarepc
|
|
|
57
65
|
> install.packages("pcalg")
|
|
58
66
|
> install.packages("https://cran.r-project.org/src/contrib/Archive/kpcalg/kpcalg_1.0.1.tar.gz")
|
|
59
67
|
```
|
|
60
|
-
|
|
68
|
+
- Then:
|
|
61
69
|
```
|
|
62
|
-
pip install
|
|
63
|
-
```
|
|
70
|
+
$ pip install timeawarepc
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To use Granger Causality, also install `nitime` (`pip install nitime`).
|
|
64
74
|
|
|
65
75
|
## Documentation
|
|
66
76
|
|
|
@@ -68,7 +78,15 @@ pip install nitime
|
|
|
68
78
|
|
|
69
79
|
## Tutorial
|
|
70
80
|
|
|
71
|
-
See the [Quick Start Guide](https://timeawarepc.readthedocs.io/en/latest/gettingstarted.html) for a quick tutorial of the main functionalities of this library and check if it is installed properly.
|
|
81
|
+
See the [Quick Start Guide](https://timeawarepc.readthedocs.io/en/latest/gettingstarted.html) for a quick tutorial of the main functionalities of this library and check if it is installed properly.
|
|
82
|
+
|
|
83
|
+
## What's new in v2.0.0
|
|
84
|
+
|
|
85
|
+
- `cfc_tpc` now defaults to **no bootstrap subsampling**: a single PC run is performed on the full time-delayed data.
|
|
86
|
+
- To use bootstrap stability scoring, pass `subsampsize` and `niter` together (e.g., `subsampsize=50, niter=25`).
|
|
87
|
+
- Both arguments must be specified together (or both left as the default `None`).
|
|
88
|
+
- `partial_corr` now fits an intercept and is shift-invariant. Previously the regression was forced through the origin, biasing residuals when the data was not mean-centered.
|
|
89
|
+
- See [CHANGELOG.md](CHANGELOG.md) for the full list of changes and migration notes.
|
|
72
90
|
<!--
|
|
73
91
|
## Documentation
|
|
74
92
|
|
|
@@ -80,7 +98,7 @@ Your help is absolutely welcome! Please do reach out or create a feature branch!
|
|
|
80
98
|
|
|
81
99
|
## Citation
|
|
82
100
|
|
|
83
|
-
Biswas, R., & Shlizerman, E. (2022). Statistical Perspective on Functional and Causal Neural Connectomics: The Time-Aware PC Algorithm. https://
|
|
101
|
+
Biswas, R., & Shlizerman, E. (2022). Statistical Perspective on Functional and Causal Neural Connectomics: The Time-Aware PC Algorithm. https://doi.org/10.1371/journal.pcbi.1010653
|
|
84
102
|
|
|
85
103
|
Biswas, R., & Shlizerman, E. (2021). Statistical Perspective on Functional and Causal Neural Connectomics: A Comparative Study. Frontiers in Systems Neuroscience. https://doi.org/10.3389/fnsys.2022.817962
|
|
86
104
|
|
|
@@ -7,17 +7,25 @@
|
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### Recommended: conda environment (handles R + kpcalg automatically)
|
|
11
11
|
|
|
12
12
|
```
|
|
13
|
-
$
|
|
13
|
+
$ git clone https://github.com/shlizee/TimeAwarePC.git
|
|
14
|
+
$ cd TimeAwarePC
|
|
15
|
+
$ conda env create -f environment.yml
|
|
16
|
+
$ conda activate timeawarepc
|
|
17
|
+
$ Rscript install_r_deps.R # installs kpcalg from CRAN archive
|
|
14
18
|
```
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
This installs Python, R, rpy2, all required R packages (graph, RBGL, pcalg), and TimeAwarePC v2.0.0 in a single isolated environment.
|
|
21
|
+
|
|
22
|
+
### Manual install (alternative)
|
|
23
|
+
|
|
24
|
+
If you prefer to install without conda:
|
|
25
|
+
|
|
26
|
+
- Python >=3.9, <3.11
|
|
27
|
+
- R >= 4.0
|
|
28
|
+
- R package ```kpcalg``` and its dependencies, installed via R or RStudio:
|
|
21
29
|
```
|
|
22
30
|
> install.packages("BiocManager")
|
|
23
31
|
> BiocManager::install("graph")
|
|
@@ -25,10 +33,12 @@ $ pip install timeawarepc
|
|
|
25
33
|
> install.packages("pcalg")
|
|
26
34
|
> install.packages("https://cran.r-project.org/src/contrib/Archive/kpcalg/kpcalg_1.0.1.tar.gz")
|
|
27
35
|
```
|
|
28
|
-
|
|
36
|
+
- Then:
|
|
29
37
|
```
|
|
30
|
-
pip install
|
|
31
|
-
```
|
|
38
|
+
$ pip install timeawarepc
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
To use Granger Causality, also install `nitime` (`pip install nitime`).
|
|
32
42
|
|
|
33
43
|
## Documentation
|
|
34
44
|
|
|
@@ -36,7 +46,15 @@ pip install nitime
|
|
|
36
46
|
|
|
37
47
|
## Tutorial
|
|
38
48
|
|
|
39
|
-
See the [Quick Start Guide](https://timeawarepc.readthedocs.io/en/latest/gettingstarted.html) for a quick tutorial of the main functionalities of this library and check if it is installed properly.
|
|
49
|
+
See the [Quick Start Guide](https://timeawarepc.readthedocs.io/en/latest/gettingstarted.html) for a quick tutorial of the main functionalities of this library and check if it is installed properly.
|
|
50
|
+
|
|
51
|
+
## What's new in v2.0.0
|
|
52
|
+
|
|
53
|
+
- `cfc_tpc` now defaults to **no bootstrap subsampling**: a single PC run is performed on the full time-delayed data.
|
|
54
|
+
- To use bootstrap stability scoring, pass `subsampsize` and `niter` together (e.g., `subsampsize=50, niter=25`).
|
|
55
|
+
- Both arguments must be specified together (or both left as the default `None`).
|
|
56
|
+
- `partial_corr` now fits an intercept and is shift-invariant. Previously the regression was forced through the origin, biasing residuals when the data was not mean-centered.
|
|
57
|
+
- See [CHANGELOG.md](CHANGELOG.md) for the full list of changes and migration notes.
|
|
40
58
|
<!--
|
|
41
59
|
## Documentation
|
|
42
60
|
|
|
@@ -48,7 +66,7 @@ Your help is absolutely welcome! Please do reach out or create a feature branch!
|
|
|
48
66
|
|
|
49
67
|
## Citation
|
|
50
68
|
|
|
51
|
-
Biswas, R., & Shlizerman, E. (2022). Statistical Perspective on Functional and Causal Neural Connectomics: The Time-Aware PC Algorithm. https://
|
|
69
|
+
Biswas, R., & Shlizerman, E. (2022). Statistical Perspective on Functional and Causal Neural Connectomics: The Time-Aware PC Algorithm. https://doi.org/10.1371/journal.pcbi.1010653
|
|
52
70
|
|
|
53
71
|
Biswas, R., & Shlizerman, E. (2021). Statistical Perspective on Functional and Causal Neural Connectomics: A Comparative Study. Frontiers in Systems Neuroscience. https://doi.org/10.3389/fnsys.2022.817962
|
|
54
72
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = timeawarepc
|
|
3
|
-
version =
|
|
3
|
+
version = 2.0.1
|
|
4
4
|
author = Rahul Biswas
|
|
5
5
|
author_email = rahul.biswas@ucsf.edu
|
|
6
6
|
description = Time-Aware PC Python Package
|
|
@@ -24,7 +24,7 @@ python_requires = >=3.7, <3.11
|
|
|
24
24
|
install_requires =
|
|
25
25
|
numpy
|
|
26
26
|
pandas
|
|
27
|
-
rpy2
|
|
27
|
+
rpy2>=3.5.11
|
|
28
28
|
networkx
|
|
29
29
|
scipy
|
|
30
30
|
|
|
@@ -3,7 +3,7 @@ import setuptools
|
|
|
3
3
|
with open('README.md','r') as fh:
|
|
4
4
|
README = fh.read()
|
|
5
5
|
|
|
6
|
-
VERSION = "
|
|
6
|
+
VERSION = "2.0.1"
|
|
7
7
|
|
|
8
8
|
setuptools.setup(
|
|
9
9
|
name = 'timeawarepc',
|
|
@@ -19,7 +19,7 @@ setuptools.setup(
|
|
|
19
19
|
install_requires=[
|
|
20
20
|
'numpy',
|
|
21
21
|
'pandas',
|
|
22
|
-
'rpy2
|
|
22
|
+
'rpy2>=3.5.11',
|
|
23
23
|
'networkx',
|
|
24
24
|
'scipy'
|
|
25
25
|
],
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Tests for the optional bootstrap behavior of cfc_tpc.
|
|
2
|
+
|
|
3
|
+
By default (subsampsize=None, niter=None), cfc_tpc should run PC once on the
|
|
4
|
+
full time-delayed data without bootstrap subsampling. When both subsampsize
|
|
5
|
+
and niter are specified, it should run bootstrap subsampling as before.
|
|
6
|
+
"""
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
# Insert project root before any installed timeawarepc so tests run against
|
|
11
|
+
# the in-repo source, not any pre-installed package version.
|
|
12
|
+
_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
13
|
+
if _PROJECT_ROOT not in sys.path:
|
|
14
|
+
sys.path.insert(0, _PROJECT_ROOT)
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# All cfc_tpc paths import rpy2. If R/rpy2 isn't available on this system, we
|
|
20
|
+
# fall back to testing the no-bootstrap signature directly via _run_pc_inner.
|
|
21
|
+
try:
|
|
22
|
+
from timeawarepc.tpc import cfc_tpc
|
|
23
|
+
HAS_RPY2 = True
|
|
24
|
+
except OSError:
|
|
25
|
+
HAS_RPY2 = False
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _make_test_data(seed: int = 0, T: int = 400, p: int = 4):
|
|
29
|
+
rng = np.random.default_rng(seed)
|
|
30
|
+
data = rng.standard_normal((T, p))
|
|
31
|
+
# Inject lagged causal structure on the first 4 vars when available.
|
|
32
|
+
for t in range(1, T):
|
|
33
|
+
if p >= 2:
|
|
34
|
+
data[t, 1] = 0.7 * data[t - 1, 0] + 0.3 * rng.standard_normal()
|
|
35
|
+
if p >= 3:
|
|
36
|
+
data[t, 2] = 0.6 * data[t - 1, 1] + 0.3 * rng.standard_normal()
|
|
37
|
+
if p >= 4:
|
|
38
|
+
data[t, 3] = 0.5 * data[t - 1, 2] + 0.3 * rng.standard_normal()
|
|
39
|
+
return data
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_cfc_tpc_default_no_bootstrap():
|
|
43
|
+
"""Default call (no subsampsize / niter) should succeed without bootstrap."""
|
|
44
|
+
if not HAS_RPY2:
|
|
45
|
+
print("SKIP test_cfc_tpc_default_no_bootstrap: rpy2/R not available")
|
|
46
|
+
return
|
|
47
|
+
data = _make_test_data()
|
|
48
|
+
adjacency, weights = cfc_tpc(data, maxdelay=1, alpha=0.1, isgauss=True)
|
|
49
|
+
assert adjacency.shape == (4, 4)
|
|
50
|
+
assert weights.shape == (4, 4)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_cfc_tpc_bootstrap_path():
|
|
54
|
+
"""Explicit bootstrap (subsampsize + niter specified) should still work."""
|
|
55
|
+
if not HAS_RPY2:
|
|
56
|
+
print("SKIP test_cfc_tpc_bootstrap_path: rpy2/R not available")
|
|
57
|
+
return
|
|
58
|
+
data = _make_test_data()
|
|
59
|
+
adjacency, weights = cfc_tpc(
|
|
60
|
+
data, maxdelay=1, alpha=0.1, isgauss=True,
|
|
61
|
+
subsampsize=50, niter=5,
|
|
62
|
+
)
|
|
63
|
+
assert adjacency.shape == (4, 4)
|
|
64
|
+
assert weights.shape == (4, 4)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_cfc_tpc_partial_bootstrap_args_raises():
|
|
68
|
+
"""Specifying only one of subsampsize/niter should raise ValueError."""
|
|
69
|
+
if not HAS_RPY2:
|
|
70
|
+
print("SKIP test_cfc_tpc_partial_bootstrap_args_raises: rpy2/R not available")
|
|
71
|
+
return
|
|
72
|
+
data = _make_test_data()
|
|
73
|
+
try:
|
|
74
|
+
cfc_tpc(data, isgauss=True, subsampsize=50)
|
|
75
|
+
except ValueError:
|
|
76
|
+
pass
|
|
77
|
+
else:
|
|
78
|
+
raise AssertionError("Expected ValueError when only subsampsize is given")
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
cfc_tpc(data, isgauss=True, niter=5)
|
|
82
|
+
except ValueError:
|
|
83
|
+
pass
|
|
84
|
+
else:
|
|
85
|
+
raise AssertionError("Expected ValueError when only niter is given")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_cfc_tpc_subsampsize_too_large_raises():
|
|
89
|
+
"""subsampsize >= number of time-delayed samples should raise ValueError."""
|
|
90
|
+
if not HAS_RPY2:
|
|
91
|
+
print("SKIP test_cfc_tpc_subsampsize_too_large_raises: rpy2/R not available")
|
|
92
|
+
return
|
|
93
|
+
data = _make_test_data(T=20, p=3)
|
|
94
|
+
try:
|
|
95
|
+
cfc_tpc(data, isgauss=True, subsampsize=1000, niter=1)
|
|
96
|
+
except ValueError:
|
|
97
|
+
pass
|
|
98
|
+
else:
|
|
99
|
+
raise AssertionError("Expected ValueError when subsampsize too large")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if __name__ == "__main__":
|
|
103
|
+
test_cfc_tpc_default_no_bootstrap()
|
|
104
|
+
test_cfc_tpc_bootstrap_path()
|
|
105
|
+
test_cfc_tpc_partial_bootstrap_args_raises()
|
|
106
|
+
test_cfc_tpc_subsampsize_too_large_raises()
|
|
107
|
+
print("All tests passed.")
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Regression test: partial_corr should be shift-invariant.
|
|
2
|
+
|
|
3
|
+
Adding a constant to the input data should not change the partial correlation,
|
|
4
|
+
because the underlying linear regression now includes an intercept term.
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
# Insert project root before any installed timeawarepc so the tests run
|
|
10
|
+
# against the in-repo source, not any pre-installed package version.
|
|
11
|
+
_PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
|
12
|
+
if _PROJECT_ROOT not in sys.path:
|
|
13
|
+
sys.path.insert(0, _PROJECT_ROOT)
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from timeawarepc.pcalg import partial_corr as partial_corr_pcalg
|
|
18
|
+
from timeawarepc.pcalg_helpers import partial_corr as partial_corr_helpers
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _make_test_data(seed: int = 0, n: int = 200):
|
|
22
|
+
rng = np.random.default_rng(seed)
|
|
23
|
+
# 3 variables; var 2 is a linear combination of vars 0 and 1 plus noise
|
|
24
|
+
data = rng.standard_normal((n, 3))
|
|
25
|
+
data[:, 2] = data[:, 0] + 0.5 * data[:, 1] + 0.1 * rng.standard_normal(n)
|
|
26
|
+
return data
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_partial_corr_pcalg_shift_invariant():
|
|
30
|
+
data = _make_test_data()
|
|
31
|
+
r_orig = partial_corr_pcalg(0, 2, {1}, data)
|
|
32
|
+
r_shift = partial_corr_pcalg(0, 2, {1}, data + 10.0)
|
|
33
|
+
assert np.isclose(r_orig, r_shift, atol=1e-8), (
|
|
34
|
+
f"partial_corr (pcalg) not shift-invariant: {r_orig} vs {r_shift}"
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_partial_corr_helpers_shift_invariant():
|
|
39
|
+
data = _make_test_data()
|
|
40
|
+
r_orig = partial_corr_helpers(data, 0, 2, {1})
|
|
41
|
+
r_shift = partial_corr_helpers(data + 10.0, 0, 2, {1})
|
|
42
|
+
assert np.isclose(r_orig, r_shift, atol=1e-8), (
|
|
43
|
+
f"partial_corr (helpers) not shift-invariant: {r_orig} vs {r_shift}"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_partial_corr_pcalg_matches_helpers():
|
|
48
|
+
"""Both partial_corr implementations should agree."""
|
|
49
|
+
data = _make_test_data()
|
|
50
|
+
r1 = partial_corr_pcalg(0, 2, {1}, data)
|
|
51
|
+
r2 = partial_corr_helpers(data, 0, 2, {1})
|
|
52
|
+
assert np.isclose(r1, r2, atol=1e-8), (
|
|
53
|
+
f"partial_corr disagreement between pcalg and helpers: {r1} vs {r2}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
test_partial_corr_pcalg_shift_invariant()
|
|
59
|
+
test_partial_corr_helpers_shift_invariant()
|
|
60
|
+
test_partial_corr_pcalg_matches_helpers()
|
|
61
|
+
print("All tests passed.")
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
"""
|
|
3
3
|
__all__ = ["find_cfc"]
|
|
4
4
|
from timeawarepc.tpc import *
|
|
5
|
-
def find_cfc(data,method_name,alpha=0.05,maxdelay=1,
|
|
5
|
+
def find_cfc(data, method_name, alpha=0.05, maxdelay=1, isgauss=False,
|
|
6
|
+
subsampsize=None, niter=None, thresh=0.25):
|
|
6
7
|
"""Estimate Causal Functional Connectivity (CFC) between nodes from time series.
|
|
7
8
|
This is a wrapper for functions cfc_tpc, cfc_pc, cfc_gc in tpc.py.
|
|
8
9
|
Refer to the individual functions for their details.
|
|
@@ -15,12 +16,16 @@ def find_cfc(data,method_name,alpha=0.05,maxdelay=1,niter=50,thresh=0.25,isgauss
|
|
|
15
16
|
'GC': Granger Causality.
|
|
16
17
|
alpha: (float) Significance level
|
|
17
18
|
isgauss: (boolean) Arg used for method_name == 'PC' or 'TPC'.
|
|
18
|
-
True: Assume Gaussian Noise distribution,
|
|
19
|
+
True: Assume Gaussian Noise distribution,
|
|
19
20
|
False: Distribution free.
|
|
20
21
|
maxdelay: (int) Maximum time-delay of interactions. Arg used for method_name == 'GC' or 'TPC'.
|
|
21
|
-
subsampsize: (int) Bootstrap window width in TPC.
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
subsampsize: (int, optional) Bootstrap window width in TPC. If None
|
|
23
|
+
(default), no bootstrap is performed and the full time-delayed
|
|
24
|
+
data is used. Must be specified together with niter.
|
|
25
|
+
niter: (int, optional) Number of bootstrap iterations in TPC. Must be
|
|
26
|
+
specified together with subsampsize.
|
|
27
|
+
thresh: (float) Bootstrap stability cut-off in TPC. Only used when
|
|
28
|
+
bootstrap is active.
|
|
24
29
|
|
|
25
30
|
Returns:
|
|
26
31
|
adjacency: (numpy.array) Adcajency matrix of estimated CFC by chosen method.
|
|
@@ -29,10 +34,13 @@ def find_cfc(data,method_name,alpha=0.05,maxdelay=1,niter=50,thresh=0.25,isgauss
|
|
|
29
34
|
"""
|
|
30
35
|
|
|
31
36
|
if method_name == 'TPC':
|
|
32
|
-
adjacency, weights = cfc_tpc(
|
|
37
|
+
adjacency, weights = cfc_tpc(
|
|
38
|
+
data, maxdelay=maxdelay, alpha=alpha, isgauss=isgauss,
|
|
39
|
+
subsampsize=subsampsize, niter=niter, thresh=thresh,
|
|
40
|
+
)
|
|
33
41
|
elif method_name == 'PC':
|
|
34
42
|
adjacency, weights = cfc_pc(data,alpha,isgauss=isgauss)
|
|
35
43
|
elif method_name == 'GC':
|
|
36
|
-
from timeawarepc.gc import cfc_gc
|
|
44
|
+
from timeawarepc.gc import cfc_gc
|
|
37
45
|
adjacency, weights = cfc_gc(data,maxdelay,alpha)
|
|
38
46
|
return adjacency,weights
|
|
@@ -419,16 +419,20 @@ def partial_corr(A,B,S,data):
|
|
|
419
419
|
for i in range(p):
|
|
420
420
|
if i in S:
|
|
421
421
|
idx[i]=True
|
|
422
|
-
C=data
|
|
423
|
-
beta_A = linalg.lstsq(C[:,idx], C[:,A])[0]
|
|
424
|
-
beta_B = linalg.lstsq(C[:,idx], C[:,B])[0]
|
|
422
|
+
C = data
|
|
425
423
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
424
|
+
# conditioning matrix: (n_samples, n_conditioning_vars)
|
|
425
|
+
# prepend a column of ones so lstsq fits an intercept -> shift-invariant
|
|
426
|
+
Z = C[:, idx]
|
|
427
|
+
Z = np.column_stack([np.ones(Z.shape[0]), Z])
|
|
428
|
+
|
|
429
|
+
beta_A = linalg.lstsq(Z, C[:, A])[0]
|
|
430
|
+
beta_B = linalg.lstsq(Z, C[:, B])[0]
|
|
431
|
+
|
|
432
|
+
res_A = C[:, A] - Z.dot(beta_A)
|
|
433
|
+
res_B = C[:, B] - Z.dot(beta_B)
|
|
434
|
+
|
|
435
|
+
return stats.pearsonr(res_A, res_B)[0]
|
|
432
436
|
if __name__ == '__main__':
|
|
433
437
|
import networkx as nx
|
|
434
438
|
import numpy as np
|
|
@@ -43,13 +43,17 @@ def partial_corr(data,A,B,S):
|
|
|
43
43
|
for i in range(p):
|
|
44
44
|
if i in S:
|
|
45
45
|
idx[i]=True
|
|
46
|
-
C=data
|
|
47
|
-
beta_A = linalg.lstsq(C[:,idx], C[:,A])[0]
|
|
48
|
-
beta_B = linalg.lstsq(C[:,idx], C[:,B])[0]
|
|
46
|
+
C = data
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
48
|
+
# conditioning matrix: (n_samples, n_conditioning_vars)
|
|
49
|
+
# prepend a column of ones so lstsq fits an intercept -> shift-invariant
|
|
50
|
+
Z = C[:, idx]
|
|
51
|
+
Z = np.column_stack([np.ones(Z.shape[0]), Z])
|
|
52
|
+
|
|
53
|
+
beta_A = linalg.lstsq(Z, C[:, A])[0]
|
|
54
|
+
beta_B = linalg.lstsq(Z, C[:, B])[0]
|
|
55
|
+
|
|
56
|
+
res_A = C[:, A] - Z.dot(beta_A)
|
|
57
|
+
res_B = C[:, B] - Z.dot(beta_B)
|
|
58
|
+
|
|
59
|
+
return stats.pearsonr(res_A, res_B)[0]
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Implements the Time-Aware PC (TPC) Algorithm for finding Causal Functional Connectivity from Time Series.
|
|
2
|
+
"""
|
|
3
|
+
import time
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from timeawarepc.tpc_helpers import *
|
|
7
|
+
from timeawarepc.pcalg import estimate_skeleton, estimate_cpdag, ci_test_gauss
|
|
8
|
+
import rpy2.robjects as robjects
|
|
9
|
+
from rpy2.robjects.packages import importr
|
|
10
|
+
import rpy2.rlike.container as rlc
|
|
11
|
+
from rpy2.robjects import pandas2ri
|
|
12
|
+
import random
|
|
13
|
+
import networkx as nx
|
|
14
|
+
import re
|
|
15
|
+
import numpy as np
|
|
16
|
+
import warnings
|
|
17
|
+
import logging
|
|
18
|
+
_logger = logging.getLogger(__name__)
|
|
19
|
+
#%%
|
|
20
|
+
def _run_pc_inner(sample, alpha, isgauss):
|
|
21
|
+
"""Run PC on a single (sub)sample of time-delayed data, return CPDAG as nx.DiGraph."""
|
|
22
|
+
if not isgauss:
|
|
23
|
+
d = {'print.me': 'print_dot_me', 'print_me': 'print_uscore_me'}
|
|
24
|
+
kpcalg = importr('kpcalg', robject_translations=d)
|
|
25
|
+
sample_pd = pd.DataFrame(sample)
|
|
26
|
+
pandas2ri.activate()
|
|
27
|
+
df = robjects.conversion.py2rpy(sample_pd)
|
|
28
|
+
base = importr("base")
|
|
29
|
+
out = kpcalg.kpc(**{
|
|
30
|
+
'suffStat': rlc.TaggedList((df, "hsic.perm"), tags=('data', 'ic.method')),
|
|
31
|
+
'indepTest': kpcalg.kernelCItest,
|
|
32
|
+
'alpha': alpha,
|
|
33
|
+
'labels': sample_pd.columns.astype(str),
|
|
34
|
+
'u2pd': "relaxed",
|
|
35
|
+
'skel.method': "stable",
|
|
36
|
+
'verbose': robjects.r('F'),
|
|
37
|
+
})
|
|
38
|
+
dollar = base.__dict__["@"]
|
|
39
|
+
graphobj = dollar(out, "graph")
|
|
40
|
+
graph = importr("graph")
|
|
41
|
+
graphedges = graph.edges(graphobj)
|
|
42
|
+
graphedgespy = {int(key): np.array(re.findall(r'-?\d+\.?\d*', str(graphedges.rx2(str(key))))).astype(int)
|
|
43
|
+
for key in graphedges.names}
|
|
44
|
+
g = nx.DiGraph(graphedgespy)
|
|
45
|
+
else:
|
|
46
|
+
(g, sep_set) = estimate_skeleton(indep_test_func=ci_test_gauss,
|
|
47
|
+
data_matrix=sample,
|
|
48
|
+
alpha=alpha, method='stable')
|
|
49
|
+
g = estimate_cpdag(skel_graph=g, sep_set=sep_set)
|
|
50
|
+
return g
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def cfc_tpc(data, maxdelay=1, alpha=0.1, isgauss=False,
|
|
54
|
+
subsampsize=None, niter=None, thresh=0.25):
|
|
55
|
+
"""Estimate Causal Functional Connectivity using TPC Algorithm.
|
|
56
|
+
|
|
57
|
+
By default (subsampsize=None, niter=None), TPC runs PC once on the full
|
|
58
|
+
time-delayed data — no bootstrap subsampling. To enable bootstrap
|
|
59
|
+
subsampling for stability scoring, pass both `subsampsize` and `niter`.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
data: (numpy.array) of shape (n,p) with n time-recordings for p nodes.
|
|
63
|
+
maxdelay: (int) Maximum time-delay of interactions.
|
|
64
|
+
alpha: (float) Significance level for conditional independence tests.
|
|
65
|
+
isgauss: (boolean)
|
|
66
|
+
True: Assume Gaussian Noise distribution.
|
|
67
|
+
False: Distribution-free.
|
|
68
|
+
subsampsize: (int, optional) Bootstrap window width. If None (default),
|
|
69
|
+
no bootstrap is performed and the full time-delayed data is used.
|
|
70
|
+
Must be specified together with `niter`.
|
|
71
|
+
niter: (int, optional) Number of bootstrap iterations. Must be specified
|
|
72
|
+
together with `subsampsize`. Ignored when subsampsize is None.
|
|
73
|
+
thresh: (float) Bootstrap stability cut-off. Only used when bootstrap
|
|
74
|
+
is active (both subsampsize and niter specified).
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
adjacency: (numpy.array) Adjacency matrix of shape (p,p) for estimated CFC by TPC Algorithm.
|
|
78
|
+
weights: (numpy.array) Connectivity Weight matrix of shape (p,p).
|
|
79
|
+
|
|
80
|
+
Biswas, Rahul and Shlizerman, Eli (2022). Statistical perspective on functional and causal neural connectomics: the time-aware pc algorithm. arXiv preprint arXiv:2204.04845.
|
|
81
|
+
"""
|
|
82
|
+
if (subsampsize is None) != (niter is None):
|
|
83
|
+
raise ValueError(
|
|
84
|
+
"subsampsize and niter must both be specified (for bootstrap) "
|
|
85
|
+
"or both be None (for no bootstrap)."
|
|
86
|
+
)
|
|
87
|
+
use_bootstrap = subsampsize is not None
|
|
88
|
+
|
|
89
|
+
start_time = time.time()
|
|
90
|
+
data_trans = data_transformed(data, maxdelay) # Step 1: Time-Delayed Samples
|
|
91
|
+
_logger.debug("Data transformed in " + str(time.time() - start_time))
|
|
92
|
+
|
|
93
|
+
if not use_bootstrap:
|
|
94
|
+
# No-bootstrap path: run PC once on the full time-delayed data.
|
|
95
|
+
g = _run_pc_inner(data_trans, alpha, isgauss)
|
|
96
|
+
causaleff = causaleff_ida(g, data_trans)
|
|
97
|
+
G, causaleffin, _ = return_finaledges(g, causaleff, maxdelay, data.shape[1])
|
|
98
|
+
adjacency = np.asarray(G).copy()
|
|
99
|
+
weights = np.asarray(causaleffin, dtype=float)
|
|
100
|
+
# Step 8: Pruning (keep same magnitude-based pruning as bootstrap path)
|
|
101
|
+
with warnings.catch_warnings():
|
|
102
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
103
|
+
cutoff = np.nanmax(np.abs(weights)) / 10
|
|
104
|
+
adjacency[np.abs(weights) <= cutoff] = 0
|
|
105
|
+
return adjacency, weights
|
|
106
|
+
|
|
107
|
+
# Bootstrap path (legacy / stability-scored).
|
|
108
|
+
C_iter, C_cf_iter, C_cf2_iter = [], [], []
|
|
109
|
+
n = data_trans.shape[0]
|
|
110
|
+
if n - subsampsize <= 0:
|
|
111
|
+
raise ValueError(
|
|
112
|
+
f"subsampsize ({subsampsize}) must be smaller than the "
|
|
113
|
+
f"number of time-delayed samples ({n})."
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
for inneriter in range(niter):
|
|
117
|
+
start_btrstrp = time.time()
|
|
118
|
+
_logger.debug("Starting bootstrap " + str(inneriter))
|
|
119
|
+
|
|
120
|
+
# Step 2: Select random Bootstrap window
|
|
121
|
+
r_idx = random.sample(range(n - subsampsize), 1)[0]
|
|
122
|
+
sample = data_trans[r_idx:(r_idx + subsampsize), :]
|
|
123
|
+
|
|
124
|
+
# Step 3: PC
|
|
125
|
+
g = _run_pc_inner(sample, alpha, isgauss)
|
|
126
|
+
|
|
127
|
+
# Step 5: Rolled CFC-DPGM
|
|
128
|
+
causaleff = causaleff_ida(g, data_trans)
|
|
129
|
+
G, causaleffin, causaleffin2 = return_finaledges(g, causaleff, maxdelay, data.shape[1])
|
|
130
|
+
|
|
131
|
+
C_iter.append(G)
|
|
132
|
+
C_cf_iter.append(causaleffin)
|
|
133
|
+
C_cf2_iter.append(causaleffin2)
|
|
134
|
+
_logger.debug("Bootstrap done in " + str(time.time() - start_btrstrp))
|
|
135
|
+
|
|
136
|
+
# Step 6b-c: Robust Edges and Connectivity Weights
|
|
137
|
+
adjacency = (np.mean(np.asarray(C_iter), axis=0) >= thresh).astype(int)
|
|
138
|
+
with warnings.catch_warnings():
|
|
139
|
+
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
140
|
+
weights = np.nanmean(np.where(np.asarray(C_cf_iter) != 0,
|
|
141
|
+
np.asarray(C_cf_iter), np.nan), axis=0)
|
|
142
|
+
_logger.debug("CE shape " + str(weights.shape))
|
|
143
|
+
|
|
144
|
+
# Step 8: Pruning
|
|
145
|
+
adjacency[np.abs(weights) <= np.nanmax(np.abs(weights)) / 10] = 0
|
|
146
|
+
|
|
147
|
+
return adjacency, weights
|
|
148
|
+
|
|
149
|
+
def cfc_pc(data,alpha,isgauss=False):
|
|
150
|
+
"""Estimate Causal Functional Connectivity using PC Algorithm.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
data: (numpy.array) of shape (n,p) with n samples for p nodes
|
|
154
|
+
alpha: (float) Significance level for conditional independence tests
|
|
155
|
+
isgauss: (boolean)
|
|
156
|
+
True: Assume Gaussian Noise distribution,
|
|
157
|
+
False: Distribution free.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
adjacency: (numpy.array) Adcajency matrix of shape (p,p) of estimated CFC by PC Algorithm.
|
|
161
|
+
weights: (numpy.array) Connectivity Weight matrix of shape (p,p).
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
if not isgauss:
|
|
165
|
+
d = {'print.me': 'print_dot_me', 'print_me': 'print_uscore_me'}
|
|
166
|
+
kpcalg = importr('kpcalg', robject_translations = d)
|
|
167
|
+
data_trans_pd = pd.DataFrame(data)
|
|
168
|
+
pandas2ri.activate()
|
|
169
|
+
df = robjects.conversion.py2rpy(data_trans_pd)
|
|
170
|
+
out=kpcalg.kpc(**{'suffStat' : rlc.TaggedList((df,"hsic.perm"),tags=('data','ic.method')),#robjects.r('list(data=data_trans, ic.method="hsic.perm")'),#list(data=data_trans, ic.method="hsic.perm"),
|
|
171
|
+
'indepTest' : kpcalg.kernelCItest,
|
|
172
|
+
'alpha' : alpha,
|
|
173
|
+
'labels' : data_trans_pd.columns.astype(str),
|
|
174
|
+
'u2pd' : "relaxed",
|
|
175
|
+
'skel.method' : "stable",
|
|
176
|
+
'verbose' : robjects.r('F')})
|
|
177
|
+
base=importr("base")
|
|
178
|
+
dollar = base.__dict__["@"]
|
|
179
|
+
graphobj=dollar(out, "graph")
|
|
180
|
+
graph=importr("graph")
|
|
181
|
+
graphedges=graph.edges(graphobj)#, "matrix")
|
|
182
|
+
import re
|
|
183
|
+
graphedgespy={int(key): np.array(re.findall(r'-?\d+\.?\d*', str(graphedges.rx2(key)))[1:]).astype(int) for key in graphedges.names}
|
|
184
|
+
g=nx.DiGraph(graphedgespy)
|
|
185
|
+
else:
|
|
186
|
+
(g, sep_set) = estimate_skeleton(indep_test_func=ci_test_gauss,
|
|
187
|
+
data_matrix=data,
|
|
188
|
+
alpha=alpha,method='stable')
|
|
189
|
+
g = estimate_cpdag(skel_graph=g, sep_set=sep_set)
|
|
190
|
+
|
|
191
|
+
weights = causaleff_ida(g,data)
|
|
192
|
+
adjacency=nx.adjacency_matrix(g).toarray()
|
|
193
|
+
return adjacency, weights*adjacency
|
|
194
|
+
# %%
|
|
@@ -44,9 +44,9 @@ alpha = 0.05
|
|
|
44
44
|
isgauss = (model == 'lingauss')
|
|
45
45
|
if method_name == 'TPC':
|
|
46
46
|
maxdelay=1
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
adjmat, causaleffmat = cfc_tpc(data,maxdelay=maxdelay,alpha=alpha,
|
|
47
|
+
# Default: no bootstrap, use full time-delayed data.
|
|
48
|
+
# To enable bootstrap stability scoring, pass subsampsize and niter together.
|
|
49
|
+
adjmat, causaleffmat = cfc_tpc(data, maxdelay=maxdelay, alpha=alpha, isgauss=isgauss)
|
|
50
50
|
elif method_name == 'PC':
|
|
51
51
|
adjmat, causaleffmat = cfc_pc(data,alpha,isgauss=isgauss)
|
|
52
52
|
elif method_name == 'GC':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: timeawarepc
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: Time-Aware PC Python Package
|
|
5
5
|
Home-page: https://github.com/biswasr/TimeAwarePC
|
|
6
6
|
Author: Rahul Biswas
|
|
@@ -15,7 +15,7 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: numpy
|
|
17
17
|
Requires-Dist: pandas
|
|
18
|
-
Requires-Dist: rpy2
|
|
18
|
+
Requires-Dist: rpy2>=3.5.11
|
|
19
19
|
Requires-Dist: networkx
|
|
20
20
|
Requires-Dist: scipy
|
|
21
21
|
Dynamic: author
|
|
@@ -39,17 +39,25 @@ Dynamic: summary
|
|
|
39
39
|
|
|
40
40
|
## Installation
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
### Recommended: conda environment (handles R + kpcalg automatically)
|
|
43
43
|
|
|
44
44
|
```
|
|
45
|
-
$
|
|
45
|
+
$ git clone https://github.com/shlizee/TimeAwarePC.git
|
|
46
|
+
$ cd TimeAwarePC
|
|
47
|
+
$ conda env create -f environment.yml
|
|
48
|
+
$ conda activate timeawarepc
|
|
49
|
+
$ Rscript install_r_deps.R # installs kpcalg from CRAN archive
|
|
46
50
|
```
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
This installs Python, R, rpy2, all required R packages (graph, RBGL, pcalg), and TimeAwarePC v2.0.0 in a single isolated environment.
|
|
53
|
+
|
|
54
|
+
### Manual install (alternative)
|
|
55
|
+
|
|
56
|
+
If you prefer to install without conda:
|
|
57
|
+
|
|
58
|
+
- Python >=3.9, <3.11
|
|
59
|
+
- R >= 4.0
|
|
60
|
+
- R package ```kpcalg``` and its dependencies, installed via R or RStudio:
|
|
53
61
|
```
|
|
54
62
|
> install.packages("BiocManager")
|
|
55
63
|
> BiocManager::install("graph")
|
|
@@ -57,10 +65,12 @@ $ pip install timeawarepc
|
|
|
57
65
|
> install.packages("pcalg")
|
|
58
66
|
> install.packages("https://cran.r-project.org/src/contrib/Archive/kpcalg/kpcalg_1.0.1.tar.gz")
|
|
59
67
|
```
|
|
60
|
-
|
|
68
|
+
- Then:
|
|
61
69
|
```
|
|
62
|
-
pip install
|
|
63
|
-
```
|
|
70
|
+
$ pip install timeawarepc
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
To use Granger Causality, also install `nitime` (`pip install nitime`).
|
|
64
74
|
|
|
65
75
|
## Documentation
|
|
66
76
|
|
|
@@ -68,7 +78,15 @@ pip install nitime
|
|
|
68
78
|
|
|
69
79
|
## Tutorial
|
|
70
80
|
|
|
71
|
-
See the [Quick Start Guide](https://timeawarepc.readthedocs.io/en/latest/gettingstarted.html) for a quick tutorial of the main functionalities of this library and check if it is installed properly.
|
|
81
|
+
See the [Quick Start Guide](https://timeawarepc.readthedocs.io/en/latest/gettingstarted.html) for a quick tutorial of the main functionalities of this library and check if it is installed properly.
|
|
82
|
+
|
|
83
|
+
## What's new in v2.0.0
|
|
84
|
+
|
|
85
|
+
- `cfc_tpc` now defaults to **no bootstrap subsampling**: a single PC run is performed on the full time-delayed data.
|
|
86
|
+
- To use bootstrap stability scoring, pass `subsampsize` and `niter` together (e.g., `subsampsize=50, niter=25`).
|
|
87
|
+
- Both arguments must be specified together (or both left as the default `None`).
|
|
88
|
+
- `partial_corr` now fits an intercept and is shift-invariant. Previously the regression was forced through the origin, biasing residuals when the data was not mean-centered.
|
|
89
|
+
- See [CHANGELOG.md](CHANGELOG.md) for the full list of changes and migration notes.
|
|
72
90
|
<!--
|
|
73
91
|
## Documentation
|
|
74
92
|
|
|
@@ -80,7 +98,7 @@ Your help is absolutely welcome! Please do reach out or create a feature branch!
|
|
|
80
98
|
|
|
81
99
|
## Citation
|
|
82
100
|
|
|
83
|
-
Biswas, R., & Shlizerman, E. (2022). Statistical Perspective on Functional and Causal Neural Connectomics: The Time-Aware PC Algorithm. https://
|
|
101
|
+
Biswas, R., & Shlizerman, E. (2022). Statistical Perspective on Functional and Causal Neural Connectomics: The Time-Aware PC Algorithm. https://doi.org/10.1371/journal.pcbi.1010653
|
|
84
102
|
|
|
85
103
|
Biswas, R., & Shlizerman, E. (2021). Statistical Perspective on Functional and Causal Neural Connectomics: A Comparative Study. Frontiers in Systems Neuroscience. https://doi.org/10.3389/fnsys.2022.817962
|
|
86
104
|
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
"""Implements the Time-Aware PC (TPC) Algorithm for finding Causal Functional Connectivity from Time Series.
|
|
2
|
-
"""
|
|
3
|
-
import time
|
|
4
|
-
import numpy as np
|
|
5
|
-
import pandas as pd
|
|
6
|
-
from timeawarepc.tpc_helpers import *
|
|
7
|
-
from timeawarepc.pcalg import estimate_skeleton, estimate_cpdag, ci_test_gauss
|
|
8
|
-
import rpy2.robjects as robjects
|
|
9
|
-
from rpy2.robjects.packages import importr
|
|
10
|
-
import rpy2.rlike.container as rlc
|
|
11
|
-
from rpy2.robjects import pandas2ri
|
|
12
|
-
import random
|
|
13
|
-
import networkx as nx
|
|
14
|
-
import re
|
|
15
|
-
import numpy as np
|
|
16
|
-
import warnings
|
|
17
|
-
import logging
|
|
18
|
-
_logger = logging.getLogger(__name__)
|
|
19
|
-
#%%
|
|
20
|
-
def cfc_tpc(data,maxdelay=1,subsampsize=50,niter=25,alpha=0.1,thresh=0.25,isgauss=False):
|
|
21
|
-
"""Estimate Causal Functional Connectivity using TPC Algorithm.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
data: (numpy.array) of shape (n,p) with n time-recordings for p nodes .
|
|
25
|
-
maxdelay: (int) Maximum time-delay of interactions.
|
|
26
|
-
subsampsize: (int) Bootstrap window width.
|
|
27
|
-
niter: (int) Number of bootstrap iterations.
|
|
28
|
-
alpha: (float) Significance level for conditional independence tests.
|
|
29
|
-
thresh: (float) Bootstrap stability cut-off.
|
|
30
|
-
isgauss: (boolean)
|
|
31
|
-
True: Assume Gaussian Noise distribution.
|
|
32
|
-
False: Distribution-free.
|
|
33
|
-
|
|
34
|
-
Returns:
|
|
35
|
-
adjacency: (numpy.array) Adcajency matrix of shape (p,p) for estimated CFC by TPC Algorithm.
|
|
36
|
-
weights: (numpy.array) Connectivity Weight matrix of shape (p,p).
|
|
37
|
-
|
|
38
|
-
Biswas, Rahul and Shlizerman, Eli (2022). Statistical perspective on functional and causal neural connectomics: the time-aware pc algorithm. arXiv preprint arXiv:2204.04845.
|
|
39
|
-
"""
|
|
40
|
-
C_iter=[]
|
|
41
|
-
C_cf_iter=[]
|
|
42
|
-
C_cf2_iter=[]
|
|
43
|
-
start_time = time.time()
|
|
44
|
-
data_trans = data_transformed(data, maxdelay)#Step 1: Time-Delayed Samples
|
|
45
|
-
_logger.debug("Data transformed in "+str(time.time()-start_time))
|
|
46
|
-
|
|
47
|
-
#Steps 2-6a:
|
|
48
|
-
for inneriter in range(niter):
|
|
49
|
-
start_btrstrp = time.time()
|
|
50
|
-
_logger.debug("Starting bootstrap "+str(inneriter))
|
|
51
|
-
n=data_trans.shape[0]
|
|
52
|
-
|
|
53
|
-
#Step 2: Select random Bootstrap window
|
|
54
|
-
r_idx = random.sample(range(n-subsampsize),1)[0]
|
|
55
|
-
|
|
56
|
-
#Step 3: PC
|
|
57
|
-
if not isgauss:
|
|
58
|
-
d = {'print.me': 'print_dot_me', 'print_me': 'print_uscore_me'}
|
|
59
|
-
kpcalg = importr('kpcalg', robject_translations = d)
|
|
60
|
-
data_trans_pd=pd.DataFrame(data_trans[r_idx:(r_idx+subsampsize),:])
|
|
61
|
-
pandas2ri.activate()
|
|
62
|
-
df = robjects.conversion.py2rpy(data_trans_pd)
|
|
63
|
-
base=importr("base")
|
|
64
|
-
out=kpcalg.kpc(**{'suffStat' : rlc.TaggedList((df,"hsic.perm"),tags=('data','ic.method')),
|
|
65
|
-
'indepTest' : kpcalg.kernelCItest,
|
|
66
|
-
'alpha' : alpha,
|
|
67
|
-
'labels' : data_trans_pd.columns.astype(str),
|
|
68
|
-
'u2pd' : "relaxed",
|
|
69
|
-
'skel.method' : "stable",
|
|
70
|
-
'verbose' : robjects.r('F')})
|
|
71
|
-
dollar = base.__dict__["@"]
|
|
72
|
-
graphobj=dollar(out, "graph")
|
|
73
|
-
graph=importr("graph")
|
|
74
|
-
graphedges=graph.edges(graphobj)#, "matrix")
|
|
75
|
-
graphedgespy={int(key): np.array(re.findall(r'-?\d+\.?\d*', str(graphedges.rx2(str(key))))).astype(int) for key in graphedges.names}
|
|
76
|
-
g=nx.DiGraph(graphedgespy)
|
|
77
|
-
else:
|
|
78
|
-
data_trans_pd=data_trans[r_idx:(r_idx+subsampsize),:]
|
|
79
|
-
(g, sep_set) = estimate_skeleton(indep_test_func=ci_test_gauss,
|
|
80
|
-
data_matrix=data_trans_pd,
|
|
81
|
-
alpha=alpha,method='stable')
|
|
82
|
-
g = estimate_cpdag(skel_graph=g, sep_set=sep_set)
|
|
83
|
-
|
|
84
|
-
#Step 4: Orient - update: not needed
|
|
85
|
-
#g=orient(g,maxdelay,data.shape[1])
|
|
86
|
-
|
|
87
|
-
#Step 5: Rolled CFC-DPGM
|
|
88
|
-
causaleff = causaleff_ida(g,data_trans)#Interventional Causal Effects in Unrolled DAG
|
|
89
|
-
G,causaleffin, causaleffin2=return_finaledges(g,causaleff,maxdelay,data.shape[1])#Rolled CFC-DPGM
|
|
90
|
-
|
|
91
|
-
A_rr=G
|
|
92
|
-
C_iter.append(A_rr)
|
|
93
|
-
C_cf_iter.append(causaleffin)
|
|
94
|
-
C_cf2_iter.append(causaleffin2)
|
|
95
|
-
_logger.debug("Bootstrap done in "+str(time.time()-start_btrstrp))
|
|
96
|
-
#Step 6a: Repeat Steps 2-5
|
|
97
|
-
|
|
98
|
-
#Step 6b-c: Robust Edges and Connectivity Weights
|
|
99
|
-
adjacency=(np.mean(np.asarray(C_iter),axis=0)>=thresh).astype(int)#Robust Edges
|
|
100
|
-
with warnings.catch_warnings():
|
|
101
|
-
warnings.simplefilter("ignore", category=RuntimeWarning)
|
|
102
|
-
weights=np.nanmean(np.where(np.asarray(C_cf_iter)!=0,np.asarray(C_cf_iter),np.nan),axis=0)#Robust Connectivity Weights
|
|
103
|
-
_logger.debug("CE shape "+str(weights.shape))
|
|
104
|
-
|
|
105
|
-
#Step 8: Pruning
|
|
106
|
-
adjacency[np.abs(weights) <= np.nanmax(np.abs(weights))/10]=0
|
|
107
|
-
|
|
108
|
-
return adjacency, weights
|
|
109
|
-
|
|
110
|
-
def cfc_pc(data,alpha,isgauss=False):
|
|
111
|
-
"""Estimate Causal Functional Connectivity using PC Algorithm.
|
|
112
|
-
|
|
113
|
-
Args:
|
|
114
|
-
data: (numpy.array) of shape (n,p) with n samples for p nodes
|
|
115
|
-
alpha: (float) Significance level for conditional independence tests
|
|
116
|
-
isgauss: (boolean)
|
|
117
|
-
True: Assume Gaussian Noise distribution,
|
|
118
|
-
False: Distribution free.
|
|
119
|
-
|
|
120
|
-
Returns:
|
|
121
|
-
adjacency: (numpy.array) Adcajency matrix of shape (p,p) of estimated CFC by PC Algorithm.
|
|
122
|
-
weights: (numpy.array) Connectivity Weight matrix of shape (p,p).
|
|
123
|
-
|
|
124
|
-
"""
|
|
125
|
-
if not isgauss:
|
|
126
|
-
d = {'print.me': 'print_dot_me', 'print_me': 'print_uscore_me'}
|
|
127
|
-
kpcalg = importr('kpcalg', robject_translations = d)
|
|
128
|
-
data_trans_pd = pd.DataFrame(data)
|
|
129
|
-
pandas2ri.activate()
|
|
130
|
-
df = robjects.conversion.py2rpy(data_trans_pd)
|
|
131
|
-
out=kpcalg.kpc(**{'suffStat' : rlc.TaggedList((df,"hsic.perm"),tags=('data','ic.method')),#robjects.r('list(data=data_trans, ic.method="hsic.perm")'),#list(data=data_trans, ic.method="hsic.perm"),
|
|
132
|
-
'indepTest' : kpcalg.kernelCItest,
|
|
133
|
-
'alpha' : alpha,
|
|
134
|
-
'labels' : data_trans_pd.columns.astype(str),
|
|
135
|
-
'u2pd' : "relaxed",
|
|
136
|
-
'skel.method' : "stable",
|
|
137
|
-
'verbose' : robjects.r('F')})
|
|
138
|
-
base=importr("base")
|
|
139
|
-
dollar = base.__dict__["@"]
|
|
140
|
-
graphobj=dollar(out, "graph")
|
|
141
|
-
graph=importr("graph")
|
|
142
|
-
graphedges=graph.edges(graphobj)#, "matrix")
|
|
143
|
-
import re
|
|
144
|
-
graphedgespy={int(key): np.array(re.findall(r'-?\d+\.?\d*', str(graphedges.rx2(key)))[1:]).astype(int) for key in graphedges.names}
|
|
145
|
-
g=nx.DiGraph(graphedgespy)
|
|
146
|
-
else:
|
|
147
|
-
(g, sep_set) = estimate_skeleton(indep_test_func=ci_test_gauss,
|
|
148
|
-
data_matrix=data,
|
|
149
|
-
alpha=alpha,method='stable')
|
|
150
|
-
g = estimate_cpdag(skel_graph=g, sep_set=sep_set)
|
|
151
|
-
|
|
152
|
-
weights = causaleff_ida(g,data)
|
|
153
|
-
adjacency=nx.adjacency_matrix(g).toarray()
|
|
154
|
-
return adjacency, weights*adjacency
|
|
155
|
-
# %%
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|