import numpy as np
import pandas as pd
from .entropy_shannon import entropy_shannon
[docs]
def entropy_ofentropy(signal, scale=10, bins=10, **kwargs):
"""**Entropy of entropy (EnofEn)**
Entropy of entropy (EnofEn or EoE) combines the features of :func:`MSE <entropy_multiscale>`
with an alternate measure of information, called *superinformation*, used in DNA sequencing.
Parameters
----------
signal : Union[list, np.array, pd.Series]
The signal (i.e., a time series) in the form of a vector of values.
scale : int
The size of the windows that the signal is divided into. Also referred to as Tau
:math:`\\tau`, it represents the scale factor and corresponds to
the amount of coarsegraining.
bins : int
The number of equal-size bins to divide the signal's range in.
**kwargs : optional
Other keyword arguments, such as the logarithmic ``base`` to use for
:func:`entropy_shannon`.
Returns
--------
enofen : float
The Entropy of entropy of the signal.
info : dict
A dictionary containing additional information regarding the parameters used, such as the
average entropy ``AvEn``.
See Also
--------
entropy_shannon, entropy_multiscale
Examples
----------
.. ipython:: python
import neurokit2 as nk
# Simulate a Signal
signal = nk.signal_simulate(duration=2, sampling_rate=200, frequency=[5, 6], noise=0.5)
# EnofEn
enofen, _ = nk.entropy_ofentropy(signal, scale=10, bins=10)
enofen
References
-----------
* Hsu, C. F., Wei, S. Y., Huang, H. P., Hsu, L., Chi, S., & Peng, C. K. (2017). Entropy of
entropy: Measurement of dynamical complexity for biological systems. Entropy, 19(10), 550.
"""
# 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."
)
info = {"Scale": scale, "Bins": bins}
# divide a one-dimensional discrete time series of length n into consecutive
# non-overlapping windows w where each window is of length 'scale'
n_windows = int(np.floor(len(signal) / scale))
windows = np.reshape(signal[: n_windows * scale], (n_windows, scale))
# Divide the range into s1 slices into n equal width bins corresponding to a discrete state k
sigrange = (np.min(signal), np.max(signal))
edges = np.linspace(sigrange[0], sigrange[1], bins + 1)
# Compute the probability for a sample in each window to occur in state k
freq = [np.histogram(windows[w, :], edges)[0] for w in range(n_windows)]
# Next, we calculate the Shannon entropy value of each window.
shanens = [entropy_shannon(freq=w / w.sum(), **kwargs)[0] for w in freq]
info["AvEn"] = np.nanmean(shanens)
# Number of unique ShanEn values (depending on the scale)
_, freq2 = np.unique(np.round(shanens, 12), return_counts=True)
freq2 = freq2 / freq2.sum()
# Shannon entropy again to measure the degree of the "changing"
enofen, _ = entropy_shannon(freq=freq2, **kwargs)
return enofen, info