# Source code for neurokit2.complexity.entropy_grid

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

from .entropy_shannon import entropy_shannon

[docs]
def entropy_grid(signal, delay=1, k=3, show=False, **kwargs):
"""**Grid Entropy (GridEn)**

Grid Entropy (GridEn or GDEn) is defined as a gridded descriptor of a :func:Poincaré plot <.hrv_nonlinear>,
which is a two-dimensional phase space diagram of a time series that plots the present sample
of a time series with respect to their delayed values. The plot is divided into :math:n*n
grids, and the :func:Shannon entropy <entropy_shannon> is computed from the probability
distribution of the number of points in each grid.

Yan et al. (2019) define two novel measures, namely **GridEn** and **Gridded Distribution Rate
(GDR)**, the latter being the percentage of grids containing points.

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.
k : int
The number of sections that the Poincaré plot is divided into. It is a coarse
graining parameter that defines how fine the grid is.
show : bool
Plot the Poincaré plot.
**kwargs : optional
Other keyword arguments, such as the logarithmic base to use for
:func:entropy_shannon.

Returns
-------
griden : float
Grid Entropy of the signal.
info : dict
A dictionary containing additional information regarding the parameters used.

--------
entropy_shannon, .hrv_nonlinear, entropy_phase

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)

# Compute Grid Entropy
@savefig p_entropy_grid1.png scale=100%
phasen, info = nk.entropy_grid(signal, k=3, show=True)
@suppress
plt.close()

.. ipython:: python

phasen

@savefig p_entropy_grid2.png scale=100%
phasen, info = nk.entropy_grid(signal, k=10, show=True)
@suppress
plt.close()

.. ipython:: python

info["GDR"]

References
----------
* Yan, C., Li, P., Liu, C., Wang, X., Yin, C., & Yao, L. (2019). Novel gridded descriptors of
poincaré plot for analyzing heartbeat interval time-series. Computers in biology and
medicine, 109, 280-289.

"""
# 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 = {"k": k, "Delay": delay}

# Normalization
Sig_n = (signal - min(signal)) / np.ptp(signal)

# Poincaré Plot
Temp = np.array([Sig_n[:-delay], Sig_n[delay:]])

# Get count of points in each grid
hist, _, _ = np.histogram2d(Temp[0, :], Temp[1, :], k)

# Get frequency
freq = np.flipud(hist.T) / hist.sum()
freq = freq[freq > 0]

# Compute Shannon Entropy
griden, _ = entropy_shannon(freq=freq, **kwargs)

# Compute Gridded Distribution Rate
info["GDR"] = np.sum(hist != 0) / hist.size

if show is True:

gridlines = np.linspace(0, 1, k + 1)
plt.subplots(1, 2)
x1 = plt.subplot(121)
ax1 = plt.axes(x1)

ax1.plot(Sig_n[:-delay], Sig_n[delay:], ".", color="#009688")
ax1.plot(
np.tile(gridlines, (2, 1)),
np.array((np.zeros(k + 1), np.ones(k + 1))),
color="red",
)
ax1.plot(
np.array((np.zeros(k + 1), np.ones(k + 1))),
np.tile(gridlines, (2, 1)),
color="red",
)
ax1.plot([0, 1], [0, 1], "k")
ax1.set_aspect("equal", "box")
ax1.set_xlabel(r"$X_{i}$")
ax1.set_ylabel(r"$X_{i} + \tau$")
ax1.set_xticks([0, 1])
ax1.set_yticks([0, 1])
ax1.set_xlim([0, 1])
ax1.set_ylim([0, 1])

x2 = plt.subplot(122)
ax2 = plt.axes(x2)
ax2.imshow(np.fliplr(hist), cmap="rainbow", aspect="equal")
ax1.set_xlabel(r"$X_{i}$")
ax1.set_ylabel(r"$X_{i} + \tau$")
ax2.set_xticks([])
ax2.set_yticks([])
plt.suptitle("Gridded Poincaré Plot and its Density")

return griden, info