Skip to content

Commit 098722d

Browse files
authored
Merge pull request #442 from ThomasMBury/development
Add feature to compute various measures of entropy
2 parents 1ea2de2 + 8b4faa9 commit 098722d

File tree

3 files changed

+408
-14
lines changed

3 files changed

+408
-14
lines changed

ewstools/core.py

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import plotly.graph_objects as go
5555
from plotly.subplots import make_subplots
5656

57+
import EntropyHub as EH
5758

5859
# For deprecating old functions
5960
import deprecation
@@ -66,7 +67,8 @@
6667

6768
class TimeSeries:
6869
"""
69-
Univariate time series data on which to compute early warning signals.
70+
Class to hold univariate time series data on which to
71+
compute early warning signals.
7072
7173
Parameters
7274
----------
@@ -116,7 +118,7 @@ def __init__(self, data, transition=None):
116118
def detrend(self, method="Gaussian", bandwidth=0.2, span=0.2):
117119
"""
118120
Detrend the time series using a chosen method.
119-
Add column to the dataframe for 'smoothing' and 'residuals'
121+
Output is stored in the self.state dataframe.
120122
121123
Parameters
122124
----------
@@ -182,8 +184,7 @@ def compute_var(self, rolling_window=0.25):
182184
Compute variance over a rolling window.
183185
If residuals have not been computed, computation will be
184186
performed over state variable.
185-
186-
Put into 'ews' dataframe
187+
Output is stored in the self.ews dataframe.
187188
188189
Parameters
189190
----------
@@ -224,8 +225,8 @@ def compute_std(self, rolling_window=0.25):
224225
Compute standard deviation over a rolling window.
225226
If residuals have not been computed, computation will be
226227
performed over state variable.
228+
Output is stored in the self.ews dataframe.
227229
228-
Put into 'ews' dataframe
229230
230231
Parameters
231232
----------
@@ -268,8 +269,7 @@ def compute_cv(self, rolling_window=0.25):
268269
mean of the state variable.
269270
If residuals have not been computed, computation will be
270271
performed over state variable.
271-
272-
Put into 'ews' dataframe
272+
Output is stored in the self.ews dataframe.
273273
274274
Parameters
275275
----------
@@ -313,7 +313,10 @@ def compute_cv(self, rolling_window=0.25):
313313

314314
def compute_auto(self, rolling_window=0.25, lag=1):
315315
"""
316-
Compute autocorrelation over a rolling window. Add to dataframe.
316+
Compute autocorrelation at a give lag over a rolling window.
317+
If residuals have not been computed, computation will be
318+
performed over state variable.
319+
Output is stored in the self.ews dataframe.
317320
318321
Parameters
319322
----------
@@ -364,8 +367,8 @@ def compute_skew(self, rolling_window=0.25):
364367
Compute skew over a rolling window.
365368
If residuals have not been computed, computation will be
366369
performed over state variable.
370+
Output is stored in the self.ews dataframe.
367371
368-
Add to dataframe.
369372
370373
Parameters
371374
----------
@@ -406,8 +409,8 @@ def compute_kurt(self, rolling_window=0.25):
406409
Compute kurtosis over a rolling window.
407410
If residuals have not been computed, computation will be
408411
performed over state variable.
412+
Output is stored in the self.ews dataframe.
409413
410-
Add to dataframe.
411414
412415
Parameters
413416
----------
@@ -443,6 +446,99 @@ def compute_kurt(self, rolling_window=0.25):
443446

444447
self.ews["kurtosis"] = kurt_values
445448

449+
def compute_entropy(self, rolling_window=0.25, method="sample", **kwargs):
450+
"""
451+
Compute entropy using a given method over a rolling window.
452+
Uses EntropyHub https://www.entropyhub.xyz/Home.html.
453+
If residuals have not been computed, computation will be
454+
performed over state variable.
455+
Output is stored in the self.ews dataframe.
456+
457+
Parameters
458+
----------
459+
rolling_window : float
460+
Length of rolling window used to compute variance. Can be specified
461+
as an absolute value or as a proportion of the length of the
462+
data being analysed. Default is 0.25.
463+
method : str
464+
The method by which to compute entropy. Options include 'sample',
465+
'approximate', and 'kolmogorov'
466+
**kwargs
467+
Keyword arguments for the EntropyHub function
468+
469+
Returns
470+
-------
471+
None.
472+
473+
"""
474+
475+
# Get time series data prior to transition
476+
if self.transition:
477+
df_pre = self.state[self.state.index <= self.transition]
478+
else:
479+
df_pre = self.state
480+
481+
# Get absolute size of rollling window if given as a proportion
482+
if 0 < rolling_window <= 1:
483+
rw_absolute = int(rolling_window * len(df_pre))
484+
else:
485+
rw_absolute = rolling_window
486+
487+
# Get data on which to compute entropy
488+
if "residuals" in df_pre.columns:
489+
ts_vals = df_pre["residuals"]
490+
else:
491+
ts_vals = df_pre["state"]
492+
493+
# Compute sample entropy
494+
if method == "sample":
495+
496+
def entropy_func(series):
497+
Samp, A, B = EH.SampEn(series.values, **kwargs)
498+
return Samp
499+
500+
# Compute approxiamte entropy
501+
if method == "approximate":
502+
503+
def entropy_func(series):
504+
Ap, Phi = EH.ApEn(series.values, **kwargs)
505+
return Ap
506+
507+
# Compute komogorov entropy
508+
if method == "kolmogorov":
509+
510+
def entropy_func(series):
511+
K2, Ci = EH.K2En(series.values, **kwargs)
512+
return K2
513+
514+
list_times = []
515+
list_entropy = []
516+
# Set up rolling window (cannot use pandas.rolling as multi-output fn)
517+
for k in np.arange(0, len(ts_vals) - (rw_absolute - 1)):
518+
# Select subset of series contained in window
519+
window_series = ts_vals.iloc[k : k + rw_absolute]
520+
# Assign the time value for the metrics (right end point of window)
521+
t_point = ts_vals.index[k + (rw_absolute - 1)]
522+
# Compute entropy (value for each channel)
523+
entropy = entropy_func(window_series)
524+
525+
list_times.append(t_point)
526+
list_entropy.append(entropy)
527+
528+
ar_entropy = np.array(list_entropy)
529+
530+
df_entropy = pd.DataFrame()
531+
df_entropy["time"] = list_times
532+
num_embedding_dims = ar_entropy.shape[1]
533+
for dim in range(num_embedding_dims):
534+
df_entropy["{}-entropy-{}".format(method, dim)] = ar_entropy[:, dim]
535+
df_entropy.set_index("time", inplace=True)
536+
537+
# Add info to self.ews dataframe
538+
for col in df_entropy.columns:
539+
self.ews[col] = df_entropy[col]
540+
# self.ews = pd.merge(self.ews, df_entropy, how="left", on="time")
541+
446542
def compute_ktau(self, tmin="earliest", tmax="latest"):
447543
"""
448544
Compute kendall tau values of CSD-based EWS.

tests/test_ewstools.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
---------------
44
"""
55

6-
76
import pytest
87
import numpy as np
98
import pandas as pd
@@ -145,10 +144,18 @@ def test_TimeSeries_ews():
145144
ts.compute_auto(lag=5, rolling_window=rolling_window)
146145
ts.compute_skew(rolling_window=rolling_window)
147146
ts.compute_kurt(rolling_window=rolling_window)
147+
ts.compute_entropy(rolling_window=rolling_window, method="sample")
148+
ts.compute_entropy(rolling_window=rolling_window, method="approximate")
149+
ts.compute_entropy(rolling_window=rolling_window, method="kolmogorov")
150+
148151
assert type(ts.ews) == pd.DataFrame
149152
assert "variance" in ts.ews.columns
150153
assert "ac5" in ts.ews.columns
151154
assert "cv" in ts.ews.columns
155+
assert "sample-entropy-0" in ts.ews.columns
156+
assert "sample-entropy-2" in ts.ews.columns
157+
assert "approximate-entropy-1" in ts.ews.columns
158+
assert "kolmogorov-entropy-1" in ts.ews.columns
152159

153160
# Compute EWS on time series without transition
154161
rolling_window = 0.5
@@ -159,10 +166,17 @@ def test_TimeSeries_ews():
159166
ts2.compute_auto(lag=5, rolling_window=rolling_window)
160167
ts2.compute_skew(rolling_window=rolling_window)
161168
ts2.compute_kurt(rolling_window=rolling_window)
169+
ts2.compute_entropy(rolling_window=rolling_window, method="sample")
170+
ts2.compute_entropy(rolling_window=rolling_window, method="approximate")
171+
ts2.compute_entropy(rolling_window=rolling_window, method="kolmogorov")
162172
assert type(ts2.ews) == pd.DataFrame
163173
assert "variance" in ts2.ews.columns
164174
assert "ac5" in ts2.ews.columns
165175
assert "cv" in ts2.ews.columns
176+
assert "sample-entropy-0" in ts2.ews.columns
177+
assert "sample-entropy-2" in ts2.ews.columns
178+
assert "approximate-entropy-1" in ts2.ews.columns
179+
assert "kolmogorov-entropy-1" in ts2.ews.columns
166180

167181
# Detrend data using Gaussian and Lowess filter
168182
ts.detrend("Gaussian", bandwidth=0.2)
@@ -181,16 +195,24 @@ def test_TimeSeries_ews():
181195
ts.compute_auto(lag=5, rolling_window=rolling_window)
182196
ts.compute_skew(rolling_window=rolling_window)
183197
ts.compute_kurt(rolling_window=rolling_window)
198+
ts.compute_entropy(rolling_window=rolling_window, method="sample")
199+
ts.compute_entropy(rolling_window=rolling_window, method="approximate")
200+
ts.compute_entropy(rolling_window=rolling_window, method="kolmogorov")
184201

185202
assert type(ts.ews) == pd.DataFrame
186203
assert "variance" in ts.ews.columns
187204
assert "ac5" in ts.ews.columns
205+
assert "sample-entropy-0" in ts.ews.columns
206+
assert "sample-entropy-2" in ts.ews.columns
207+
assert "approximate-entropy-1" in ts.ews.columns
208+
assert "kolmogorov-entropy-1" in ts.ews.columns
188209

189210
# Test kendall tau computation
190211
ts.compute_ktau()
191212
assert type(ts.ktau) == dict
192213
assert "variance" in ts.ktau.keys()
193214
assert "ac5" in ts.ktau.keys()
215+
assert "sample-entropy-0" in ts.ktau.keys()
194216

195217
# Make plotly fig
196218
fig = ts.make_plotly()
@@ -333,9 +355,7 @@ def test_shopf_init():
333355
[sigma, mu, w0] = helpers.shopf_init(smax, stot, wdom)
334356

335357
# Values that smax, stot should attain (+/- 1dp)
336-
smax_assert = (sigma**2 / (4 * np.pi * mu**2)) * (
337-
1 + (mu**2 / (mu**2 + 4 * w0**2))
338-
)
358+
smax_assert = (sigma**2 / (4 * np.pi * mu**2)) * (1 + (mu**2 / (mu**2 + 4 * w0**2)))
339359
stot_assert = -(sigma**2) / (2 * mu)
340360
wdom_assert = w0
341361

0 commit comments

Comments
 (0)