CCM (CCM v2.0.0)

View Source

Convergent Cross Mapping (CCM) implementation for detecting causality in coupled nonlinear dynamical systems.

CCM is a nonlinear time-series method introduced by Sugihara et al. (2012) that detects causal relationships between variables without requiring a parametric model. The key insight is that if variable X causally influences variable Y, then the historical values of Y contain enough information to recover the state of X — and this recovery improves as more data are added (i.e. the cross-map skill converges with library size).

Typical workflow

# 1. Build a CCM struct for the two time series
ccm = CCM.new(x_series, y_series, embedding_dim: 3, tau: 1)

# 2. (Optional) select the best embedding dimension automatically
%{optimal_dim: e} = CCM.optimal_embedding_dim(x_series)
ccm = CCM.new(x_series, y_series, embedding_dim: e)

# 3. Test causality in both directions simultaneously
%{x_causes_y: fwd, y_causes_x: rev} = CCM.bidirectional_ccm(ccm)

fwd.convergent  #=> true / false
rev.convergent  #=> true / false

Summary

Functions

Performs CCM analysis in both causal directions concurrently.

Performs CCM analysis for a single causal direction.

Creates a new CCM analysis structure.

Determines the optimal embedding dimension for a time series using simplex projection with leave-one-out (LOO) cross-validation (Sugihara & May, 1990).

Functions

bidirectional_ccm(ccm)

Performs CCM analysis in both causal directions concurrently.

Runs :x_causes_y and :y_causes_x as parallel Tasks and collects the results. This is the recommended entry point for a full causality analysis because genuine unidirectional causality produces convergence in only one direction, while bidirectional coupling produces convergence in both.

Parameters

  • ccm – a %CCM{} struct produced by new/3

Returns

A map with two keys, each containing the result map from cross_map/2:

  • :x_causes_y – result for the X → Y direction
  • :y_causes_x – result for the Y → X direction

Examples

iex> {x, y} = CoupledLogisticMapsGenerator.run(300, 0.15)
iex> ccm = CCM.new(x, y, embedding_dim: 3, tau: 1)
iex> %{x_causes_y: fwd, y_causes_x: rev} = CCM.bidirectional_ccm(ccm)
iex> is_boolean(fwd.convergent) and is_boolean(rev.convergent)
true

cross_map(ccm, direction \\ :x_causes_y)

Performs CCM analysis for a single causal direction.

Both time series are embedded and split chronologically. The first train_percent of each embedded series forms the training library; remaining points are held out for testing. For each library size, the first points of the training set are used. Neighbours are found on the source manifold and their weights are applied to the corresponding embedded vectors of the target series. The skill is the mean Pearson correlation across embedding coordinates, matching skccm.

Parameters

  • ccm – a %CCM{} struct produced by new/3
  • direction – atom indicating the causal direction to test:
    • :x_causes_y (default) – tests whether X drives Y by cross-mapping Y's manifold to recover X
    • :y_causes_x – tests whether Y drives X by cross-mapping X's manifold to recover Y

Returns

A map with:

  • :direction – the direction atom passed in
  • :results – list of {library_size, mean_correlation} tuples, one per lib size
  • :convergent – true if the skill shows a significant positive trend and the peak skill exceeds 0.3 (Sugihara et al. 2012 criterion), false otherwise

new(x_series, y_series, opts \\ [])

Creates a new CCM analysis structure.

Parameters

  • x_series – list of numeric values for variable X
  • y_series – list of numeric values for variable Y (must be the same length as x_series)
  • opts – keyword list of options:
    • :embedding_dim – embedding dimension E used to reconstruct the attractor (default: 3). Use optimal_embedding_dim/2 to choose this automatically.
    • :tau – time delay between successive embedding coordinates (default: 1). Values > 1 are useful when the time series is oversampled.
    • :train_percent – fraction of the embedded series used as the training library (default: 0.75, matching skccm).
    • :lib_sizes – explicit list of library sizes to evaluate. When omitted, sizes are generated automatically up to the training-library length.

Returns

A %CCM{} struct ready to be passed to cross_map/2 or bidirectional_ccm/1.

Raises

Examples

iex> CCM.new([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]) |> Map.get(:embedding_dim)
3

iex> CCM.new([1, 2, 3], [1, 2], []) |> Map.get(:embedding_dim)
** (ArgumentError) x_series and y_series must have the same length

optimal_embedding_dim(series, opts \\ [])

Determines the optimal embedding dimension for a time series using simplex projection with leave-one-out (LOO) cross-validation (Sugihara & May, 1990).

For each candidate embedding dimension from 1 to max_dim, the series is embedded with the given time delay tau, and each point is predicted from E+1 nearest neighbours drawn from the rest of the library (LOO). The Pearson correlation between predictions and actuals is used as the skill score. The embedding dimension that maximises this skill is returned as the optimal choice.

Parameters

  • series – list of numeric values
  • opts – keyword list of options:
    • :tau – time delay (default: 1)
    • :max_dim – largest embedding dimension to test (default: 10)

Returns

A map with:

  • :optimal_dim – the embedding dimension with the highest LOO skill
  • :skills – list of {embedding_dim, skill} tuples for every dimension tested

Examples

iex> {x, _y} = CoupledLogisticMapsGenerator.run(200, 0.3)
iex> %{optimal_dim: e} = CCM.optimal_embedding_dim(x)
iex> e in 1..10
true