Skip to content

Commit c8607c8

Browse files
authored
Merge branch 'master' into leanings
2 parents ce542de + 934cfec commit c8607c8

File tree

20 files changed

+407
-159
lines changed

20 files changed

+407
-159
lines changed

Project.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name = "CausalityTools"
22
uuid = "5520caf5-2dd7-5c5d-bfcb-a00e56ac49f7"
33
authors = ["Kristian Agasøster Haaga <kahaaga@gmail.com>", "Tor Einar Møller <temolle@gmail.com>", "George Datseris <datseris.george@gmail.com>"]
44
repo = "https://github.com/kahaaga/CausalityTools.jl.git"
5-
version = "1.0.0"
5+
version = "1.3.0"
66

77
[deps]
88
ChaosTools = "608a59af-f2a3-5ad4-90b4-758bdf3122a7"
@@ -42,8 +42,8 @@ Reexport = "0.2, 1"
4242
Requires = "^1"
4343
SimpleDiffEq = "^1"
4444
StatsBase = "^0.33"
45-
TimeseriesSurrogates = "^1.2"
46-
TransferEntropy = "^1.3.3"
45+
TimeseriesSurrogates = "^1.2, 2"
46+
TransferEntropy = "^1.7"
4747
Wavelets = "0.9"
4848
julia = "^1.6"
4949

README.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ Check out the [documentation](https://juliadynamics.github.io/CausalityTools.jl/
1111

1212
## Key tools
1313

14-
- A easy-to-use framework for estimating information theoretic measures, such as transfer entropy, predictive asymmetry.
15-
- Generalized entropy and mutual information.
16-
- Convergent cross mapping, S-measure and joint distance distribution.
14+
- A easy-to-use framework for estimating information theoretic measures, such as transfer entropy, predictive asymmetry, generalized entropy and mutual information.
15+
- Convergent cross mapping, pairwise asymmetric inference, S-measure and joint distance distribution.
1716
- Surrogate data generation.
1817

1918
## Installation

changelog.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Changelog
2+
3+
## 1.3.0
4+
5+
### Example systems
6+
7+
- Added continous example system [`repressilator`](@ref).

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[deps]
22
DelayEmbeddings = "5732040d-69e3-5649-938a-b6b4f237613f"
3+
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
34
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
45
DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8"
56
DynamicalSystems = "61744808-ddfa-5f27-97ff-6e42cc95d634"

docs/make.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ using Pkg
33
CI = get(ENV, "CI", nothing) == "true" || get(ENV, "GITHUB_TOKEN", nothing) !== nothing
44
CI && Pkg.activate(@__DIR__)
55
CI && Pkg.instantiate()
6-
CI && (ENV["GKSwstype"] = "100")
6+
ENV["GKSwstype"] = "100" # allow local builds without output
77
using DelayEmbeddings
88
using TransferEntropy
99
using Documenter
1010
using DocumenterTools: Themes
1111
using CausalityTools
1212
using DynamicalSystems
1313
using HypothesisTests
14+
using Distributions
1415

1516
# %% JuliaDynamics theme.
1617
# download the themes
@@ -40,6 +41,7 @@ PAGES = [
4041
"joint_distance_distribution.md",
4142
"s_measure.md",
4243
"cross_mapping.md",
44+
"pairwise_asymmetric_inference.md"
4345
],
4446
"Information/entropy based" => [
4547
"mutualinfo.md",

docs/src/TransferEntropy.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
# [Transfer entropy](@ref transferentropy)
22

3+
## Traditional
4+
5+
The following `transferentropy` function computes transfer entropy "manually", that is,
6+
in addition to specifying an estimator, you have to specify embedding parameters.
7+
38
```@docs
49
transferentropy
510
```
611

7-
## Reproducing Schreiber
12+
## Automated variable selection
13+
14+
The `bbnue` function optimizes embedding parameters using an iterative procedure for
15+
variable selection, and performs null hypothesis testing as part of that procedure.
16+
17+
```@docs
18+
bbnue
19+
```
20+
21+
## Example: Reproducing Schreiber (2000)
822

923
Let's try to reproduce the results from Schreiber's original paper[^Schreiber2000] on transfer entropy. We'll use a
1024
visitation frequency estimator, which computes entropies by counting visits of the system's orbit to discrete portions
@@ -101,4 +115,4 @@ savefig("ulam-te-surr.svg"); nothing # hide
101115

102116
The plot above shows the original transfer entropies (solid lines) and the 95th percentile transfer entropies of the surrogate ensembles (dotted lines). As expected, using the surrogate test, the transfer entropies from `X1` to `X2` are mostly significant (solid black line is *above* dashed black line). The transfer entropies from `X2` to `X1`, on the other hand, are mostly not significant (red solid line is *below* red dotted line).
103117

104-
[^Schreiber2000](Schreiber, Thomas. "Measuring information transfer." Physical review letters 85.2 (2000): 461.)
118+
[^Schreiber2000](Schreiber, Thomas. "Measuring information transfer." Physical review letters 85.2 (2000): 461.)

docs/src/example_systems.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ rossler_lorenz
5151
chuacircuit_nscroll_sine
5252
```
5353

54+
### Repressilator
55+
56+
```@docs
57+
repressilator
58+
```
59+
5460
## [Discrete](@id discrete_systems)
5561

5662
### [Ulam map](@id ulam)

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ properties of these delay reconstructions, to infer causal/dynamical relationshi
1616
time series, and embedding parameters (given as keyword arguments).
1717

1818
- Cross mapping ([`crossmap`](@ref))
19+
- Pairwise asymmetric inference ([`pai`](@ref))
1920
- Joint distance distribution ([`jdd`](@ref))
2021
- S-measure ([`s_measure`](@ref))
2122

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# Pairwise asymmetric inference
2+
3+
```@docs
4+
pai
5+
```
6+
7+
## Example: nonlinear system
8+
9+
Let's try to reproduce figure 8 in McCracken & Weigel (2014)[^McCracken2014]. We'll start by defining the their example B (equations 6-7). This system consists of two
10+
variables ``X`` and ``Y``, where ``X`` drives ``Y``.
11+
12+
```@example pai_ex
13+
using CausalityTools, DynamicalSystems, Plots, StatsBase, Statistics, Distributions; gr()
14+
15+
function eom_nonlinear_sindriver(dx, x, p, n)
16+
a, b, c, t, Δt = (p...,)
17+
x, y = x[1], x[2]
18+
𝒩 = Normal(0, 1)
19+
20+
dx[1] = sin(t)
21+
dx[2] = a*x * (1 - b*x) + c*rand(𝒩)
22+
p[end-1] += 1 # update t
23+
24+
return
25+
end
26+
27+
function nonlinear_sindriver(;u₀ = rand(2), a = 1.0, b = 1.0, c = 2.0, Δt = 1)
28+
DiscreteDynamicalSystem(eom_nonlinear_sindriver, u₀, [a, b, c, 0, Δt])
29+
end
30+
31+
# Create a system of nonidentical logistic maps where coupling from variable x to variable y
32+
# is stronger than vice versa.
33+
sys = nonlinear_sindriver(a = 1.0, b = 1.0, c = 2.0)
34+
npts = 100
35+
orbit = trajectory(sys, npts, Ttr = 10000)
36+
x, y = columns(orbit);
37+
plot(xlabel = "Time step", ylabel = "Value")
38+
# Standardize and plot data
39+
plot!((x .- mean(x)) ./ std(x), label = "x")
40+
plot!((y .- mean(y)) ./ std(y), label = "y")
41+
savefig("pai_ts.svg") # hide
42+
```
43+
44+
![](pai_ts.svg)
45+
46+
Now, let's generate such time series for many different values of the parameters `a` and `b`, and compute PAI for fixed `p = 2.0`. This will replicate the upper right panel of figure 8 in the original paper.
47+
48+
```@example pai_ex
49+
as = 0.25:0.25:4.0
50+
bs = 0.25:0.25:4.0
51+
52+
pai_xys = zeros(length(as), length(bs))
53+
pai_yxs = zeros(length(as), length(bs))
54+
c = 2.0
55+
npts = 2000
56+
d, τ = 2, 1
57+
for (i, a) in enumerate(as)
58+
for (j, b) in enumerate(bs)
59+
s = nonlinear_sindriver(a = a, b = a, c = c)
60+
X, Y = columns(trajectory(s, npts, Ttr = 10000))
61+
# Use the segment bootstrap estimator, take the mean of 50 reps over segments of length L = 200
62+
pai_xys[i, j] = pai(X, Y, d, τ, :segment, L = 200, nreps = 50) |> mean
63+
pai_yxs[i, j] = pai(Y, X, d, τ, :segment, L = 200, nreps = 50) |> mean
64+
end
65+
end
66+
```
67+
68+
Now that we have computed the PAI in both directions, we define a measure of directionality as the difference between PAI in the ``X \to Y`` direction and in the ``Y \to X`` direction, so that if ``X`` drives ``Y``, then ``\Delta < 0``.
69+
70+
```@example pai_ex
71+
Δ = pai_xys .- pai_yxs
72+
73+
clr = cgrad(:magma, categorical = true)
74+
plot(xlabel = "a", ylabel = "b")
75+
yticks!((1:length(as), string.(as)))
76+
xticks!((1:length(bs), string.(bs)))
77+
heatmap!(Δ, c = clr, logscale = true)
78+
savefig("heatmap_pai.svg") # hide
79+
```
80+
81+
![](heatmap_pai.svg)
82+
83+
As expected, ``\Delta < 0`` for all parameter combinations, implying that ``X`` "PAI drives" ``Y``.

docs/src/s_measure.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,18 @@ We'll also do the same for the Henon maps.
4040

4141
The test parameters are embedding dimensions (`dx` for the source and `dy` for the target), the embedding lags (`τx` for the source and `τy` for the target), and the number of nearest neighbors `K`. We'll compute the test with fixed embedding parameters, but a varying number of nearest neighbors (`ks = 2:10`).
4242

43-
For the sake of demonstration, we'll use 4-dimensional embedding with embedding lag 3 for the source, and a 5-dimensional embedding with embedding lag 1 for the target. For a real use case, these embedding parameters should be
44-
chosen more carefully.
43+
For the sake of demonstration, we'll use the same embedding parameters both for the source and for the target, for all analyses. For a real use case, embedding parameters should be chosen more carefully, and will, in general, be different for the source and for the target.
4544

4645
```@example smeasure_random_henon
47-
ks = 2:8
46+
dx, dy = 5, 5
47+
τx, τy = 1, 1
48+
4849
# Compute the s-measures for different values of k
49-
ss_r_xy = [s_measure(x, y, dx = 4, τx = 3, dy = 5, τy = 1, K = k) for k in ks]
50-
ss_r_yx = [s_measure(yr, xr, dx = 4, τx = 3, dy = 5, τy = 1, K = k) for k in ks]
51-
ss_henon_xy = [s_measure(x, y, dx = 4, τx = 3, dy = 5, τy = 1, K = k) for k in ks]
52-
ss_henon_yx = [s_measure(y, x, dx = 4, τx = 3, dy = 5, τy = 1, K = k) for k in ks]
53-
[ss_r_xy ss_r_yx ss_henon_xy ss_henon_yx]
50+
ks = 2:10
51+
ss_r_xy = [s_measure(xr, yr, dx = dx, τx = τx, dy = dy, τy = τy, K = k) for k in ks]
52+
ss_r_yx = [s_measure(yr, xr, dx = dx, τx = τx, dy = dy, τy = τy, K = k) for k in ks]
53+
ss_henon_xy = [s_measure(x, y, dx = dx, τx = τx, dy = dy, τy = τy, K = k) for k in ks]
54+
ss_henon_yx = [s_measure(y, x, dx = dx, τx = τx, dy = dy, τy = τy, K = k) for k in ks]
5455
5556
plot(xlabel = "# nearest neighbors (k)", ylabel = "S", ylims = (-0.05, 1.05))
5657
plot!(ks, ss_r_xy, label = "random uncoupled system (x -> y)", marker = stroke(2), c = :black)

0 commit comments

Comments
 (0)