# -*- coding: utf-8 -*-
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import sklearn.metrics.pairwise
from ..misc import expspace
from .utils_complexity_embedding import complexity_embedding
[docs]
def fractal_correlation(signal, delay=1, dimension=2, radius=64, show=False, **kwargs):
"""**Correlation Dimension (CD)**
The Correlation Dimension (CD, also denoted *D2*) is a lower bound estimate of the fractal
dimension of a signal.
The time series is first :func:`time-delay embedded <complexity_embedding>`, and distances
between all points in the trajectory are calculated. The "correlation sum" is then computed,
which is the proportion of pairs of points whose distance is smaller than a given radius. The
final correlation dimension is then approximated by a log-log graph of correlation sum vs. a
sequence of radiuses.
This function can be called either via ``fractal_correlation()`` or ``complexity_cd()``.
Parameters
----------
signal : Union[list, np.array, pd.Series]
The signal (i.e., a time series) in the form of a vector of values.
delay : int
Time delay (often denoted *Tau* :math:`\\tau`, sometimes referred to as *lag*) in samples.
See :func:`complexity_delay` to estimate the optimal value for this parameter.
dimension : int
Embedding Dimension (*m*, sometimes referred to as *d* or *order*). See
:func:`complexity_dimension` to estimate the optimal value for this parameter.
radius : Union[str, int, list]
The sequence of radiuses to test. If an integer is passed, will get an exponential sequence
of length ``radius`` ranging from 2.5% to 50% of the distance range. Methods implemented in
other packages can be used via ``"nolds"``, ``"Corr_Dim"`` or ``"boon2008"``.
show : bool
Plot of correlation dimension if ``True``. Defaults to ``False``.
**kwargs
Other arguments to be passed (not used for now).
Returns
----------
cd : float
The Correlation Dimension (CD) of the time series.
info : dict
A dictionary containing additional information regarding the parameters used
to compute the correlation dimension.
Examples
----------
For some completely unclear reasons, uncommenting the following examples messes up the figures
path of all the subsequent documented function. So, commenting it for now.
.. ipython:: python
import neurokit2 as nk
signal = nk.signal_simulate(duration=1, frequency=[10, 14], noise=0.1)
# @savefig p_fractal_correlation1.png scale=100%
# cd, info = nk.fractal_correlation(signal, radius=32, show=True)
# @suppress
# plt.close()
.. ipython:: python
# @savefig p_fractal_correlation2.png scale=100%
# cd, info = nk.fractal_correlation(signal, radius="nolds", show=True)
# @suppress
# plt.close()
.. ipython:: python
# @savefig p_fractal_correlation3.png scale=100%
# cd, info = nk.fractal_correlation(signal, radius='boon2008', show=True)
# @suppress
# plt.close()
References
-----------
* Bolea, J., Laguna, P., Remartínez, J. M., Rovira, E., Navarro, A., & Bailón, R. (2014).
Methodological framework for estimating the correlation dimension in HRV signals.
Computational and mathematical methods in medicine, 2014.
* Boon, M. Y., Henry, B. I., Suttle, C. M., & Dain, S. J. (2008). The correlation dimension:
A useful objective measure of the transient visual evoked potential?. Journal of vision,
8(1), 6-6.
"""
# Sanity checks
if isinstance(signal, (np.ndarray, pd.DataFrame)) and signal.ndim > 1:
raise ValueError(
"Multidimensional inputs (e.g., matrices or multichannel data) are not supported yet."
)
# Get embedded
embedded = complexity_embedding(signal, delay=delay, dimension=dimension)
dist = sklearn.metrics.pairwise.euclidean_distances(embedded)
r_vals = _fractal_correlation_get_r(radius, signal, dist)
# Store parameters
info = {"Dimension": dimension, "Delay": delay, "Radius": r_vals}
# Get only upper triang of the distance matrix to reduce computational load
upper = dist[np.triu_indices_from(dist, k=1)]
corr = np.array([np.sum(upper < r) for r in r_vals])
corr = corr / len(upper)
# filter zeros from correlation sums
r_vals = r_vals[np.nonzero(corr)[0]]
corr = corr[np.nonzero(corr)[0]]
# Compute trend
if len(corr) == 0:
return np.nan, info
else:
cd, intercept = np.polyfit(np.log2(r_vals), np.log2(corr), 1)
if show is True:
plt.figure()
plt.title("Correlation Dimension")
plt.xlabel(r"$\log_{2}$(radius)")
plt.ylabel(r"$\log_{2}$(correlation sum)")
fit = 2 ** np.polyval((cd, intercept), np.log2(r_vals))
plt.loglog(r_vals, corr, "bo")
plt.loglog(r_vals, fit, "r", label=f"$CD$ = {np.round(cd, 2)}")
plt.legend(loc="lower right")
return cd, info
# =============================================================================
# Utilities
# =============================================================================
def _fractal_correlation_get_r(radius, signal, dist):
if isinstance(radius, str):
if radius == "nolds":
sd = np.std(signal, ddof=1)
min_r, max_r, factor = 0.1 * sd, 0.5 * sd, 1.03
r_n = int(np.floor(np.log(1.0 * max_r / min_r) / np.log(factor)))
r_vals = np.array([min_r * (factor ** i) for i in range(r_n + 1)])
elif radius == "Corr_Dim":
r_min, r_max = np.min(dist[np.where(dist > 0)]), np.exp(np.floor(np.log(np.max(dist))))
n_r = int(np.floor(np.log(r_max / r_min))) + 1
ones = -1 * np.ones([n_r])
r_vals = r_max * np.exp(ones * np.arange(n_r) - ones)
elif radius == "boon2008":
r_min, r_max = np.min(dist[np.where(dist > 0)]), np.max(dist)
r_vals = r_min + np.arange(1, 65) * ((r_max - r_min) / 64)
if isinstance(radius, int):
dist_range = np.max(dist) - np.min(dist)
r_min, r_max = (np.min(dist) + 0.025 * dist_range), (np.min(dist) + 0.5 * dist_range)
r_vals = expspace(r_min, r_max, radius, base=2, out="float")
return r_vals