from warnings import warn
import numpy as np
import pandas as pd
from ..misc import NeuroKitWarning
from ..stats import standardize
[docs]
def fractal_nld(signal, corrected=False):
"""**Fractal dimension via Normalized Length Density (NLDFD)**
NLDFD is a very simple index corresponding to the average absolute consecutive
differences of the (standardized) signal (``np.mean(np.abs(np.diff(std_signal)))``).
This method was developed for measuring signal complexity of very short durations (< 30
samples), and can be used for instance when continuous signal FD changes (or "running" FD) are
of interest (by computing it on sliding windows, see example).
For methods such as Higuchi's FD, the standard deviation of the window FD increases sharply
when the epoch becomes shorter. The NLD method results in lower standard deviation especially
for shorter epochs, though at the expense of lower accuracy in average window FD.
See Also
--------
fractal_higuchi
Parameters
----------
signal : Union[list, np.array, pd.Series]
The signal (i.e., a time series) in the form of a vector of values.
corrected : bool
If ``True``, will rescale the output value according to the power model estimated by
Kalauzi et al. (2009) to make it more comparable with "true" FD range, as follows:
``FD = 1.9079*((NLD-0.097178)^0.18383)``. Note that this can result in ``np.nan`` if the
result of the difference is negative.
Returns
--------
fd : DataFrame
A dataframe containing the fractal dimension across epochs.
info : dict
A dictionary containing additional information (currently, but returned nonetheless for
consistency with other functions).
Examples
----------
**Example 1**: Usage on a short signal
.. ipython:: python
import neurokit2 as nk
# Simulate a short signal with duration of 0.5s
signal = nk.signal_simulate(duration=0.5, frequency=[3, 5])
# Compute Fractal Dimension
fd, _ = nk.fractal_nld(signal, corrected=False)
fd
**Example 2**: Compute FD-NLD on non-overlapping windows
.. ipython:: python
import numpy as np
# Simulate a long signal with duration of 5s
signal = nk.signal_simulate(duration=5, frequency=[3, 5, 10], noise=0.1)
# We want windows of size=100 (0.1s)
n_windows = len(signal) // 100 # How many windows
# Split signal into windows
windows = np.array_split(signal, n_windows)
# Compute FD-NLD on all windows
nld = [nk.fractal_nld(i, corrected=False)[0] for i in windows]
np.mean(nld) # Get average
**Example 3**: Calculate FD-NLD on sliding windows
.. ipython:: python
# Simulate a long signal with duration of 5s
signal = nk.signal_simulate(duration=5, frequency=[3, 5, 10], noise=0.1)
# Add period of noise
signal[1000:3000] = signal[1000:3000] + np.random.normal(0, 1, size=2000)
# Create function-wrapper that only return the NLD value
nld = lambda x: nk.fractal_nld(x, corrected=False)[0]
# Use them in a rolling window of 100 samples (0.1s)
rolling_nld = pd.Series(signal).rolling(100, min_periods = 100, center=True).apply(nld)
@savefig p_nld1.png scale=100%
nk.signal_plot([signal, rolling_nld], subplots=True, labels=["Signal", "FD-NLD"])
@suppress
plt.close()
References
----------
* Kalauzi, A., Bojić, T., & Rakić, L. (2009). Extracting complexity waveforms from
one-dimensional signals. Nonlinear biomedical physics, 3(1), 1-11.
"""
# 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."
)
# Amplitude normalization
signal = standardize(signal)
# Calculate normalized length density
nld = np.nanmean(np.abs(np.diff(signal)))
if corrected:
# Power model optimal parameters based on analysis of EEG signals (from Kalauzi et al. 2009)
a = 1.9079
k = 0.18383
nld_diff = nld - 0.097178 # NLD - NLD0
if nld_diff < 0:
warn(
"Normalized Length Density of the signal may be too small, retuning `np.nan`.",
category=NeuroKitWarning,
)
nld = np.nan
else:
nld = a * (nld_diff ** k)
# Compute fd
return nld, {}