complexeva 0.1.5__tar.gz → 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. {complexeva-0.1.5 → complexeva-0.1.7}/PKG-INFO +1 -1
  2. complexeva-0.1.7/complexeva/measures/__init__.py +6 -0
  3. complexeva-0.1.7/complexeva/measures/dimension_1/__init__.py +0 -0
  4. {complexeva-0.1.5/complexeva/measures → complexeva-0.1.7/complexeva/measures/dimension_1}/higuchi.py +4 -4
  5. complexeva-0.1.7/complexeva/measures/dimension_1/lempel_ziv.py +201 -0
  6. complexeva-0.1.7/complexeva/measures/dimension_2/__init__.py +0 -0
  7. complexeva-0.1.7/complexeva/measures/network_space/__init__.py +0 -0
  8. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/measures/validate.py +13 -7
  9. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva.egg-info/PKG-INFO +1 -1
  10. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva.egg-info/SOURCES.txt +7 -4
  11. {complexeva-0.1.5 → complexeva-0.1.7}/setup.py +1 -1
  12. complexeva-0.1.5/complexeva/measures/__init__.py +0 -5
  13. complexeva-0.1.5/complexeva/measures/co_activation.py +0 -100
  14. {complexeva-0.1.5 → complexeva-0.1.7}/README.md +0 -0
  15. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/__init__.py +0 -0
  16. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/dummies/__init__.py +0 -0
  17. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/dummies/matrix.py +0 -0
  18. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/dummies/networks.py +0 -0
  19. {complexeva-0.1.5/complexeva/measures → complexeva-0.1.7/complexeva/measures/dimension_2}/cbb_matrix.py +0 -0
  20. {complexeva-0.1.5/complexeva/measures → complexeva-0.1.7/complexeva/measures/network_space}/cbb_networkx.py +0 -0
  21. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/utils/__init__.py +0 -0
  22. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/utils/convert_image_matrix.py +0 -0
  23. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/utils/convert_matrix_network.py +0 -0
  24. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva/utils/device.py +0 -0
  25. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva.egg-info/dependency_links.txt +0 -0
  26. {complexeva-0.1.5 → complexeva-0.1.7}/complexeva.egg-info/top_level.txt +0 -0
  27. {complexeva-0.1.5 → complexeva-0.1.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: complexeva
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Home-page:
5
5
  Author: Don Yin
6
6
  Author-email: don_yin@outlook.com
@@ -0,0 +1,6 @@
1
+ from .dimension_1.lempel_ziv import lempel_ziv
2
+ from .dimension_1.higuchi import hfd_matlab_equivalent, hfd_pyeeg
3
+ from .dimension_2.cbb_matrix import FractalHandlerMatrix
4
+ from .network_space.cbb_networkx import FractalHandlerNetworkX
5
+
6
+ __all__ = ["lempel_ziv", "hfd_matlab_equivalent", "hfd_pyeeg", "FractalHandlerMatrix", "FractalHandlerNetworkX"]
@@ -8,7 +8,7 @@ from donware import inspect_package
8
8
  from numba import njit
9
9
 
10
10
 
11
- def hfd_matlab_equivalent(data, k_max):
11
+ def hfd_matlab_equivalent(data, k_max=16):
12
12
  """
13
13
  [translated from MATLAB code]
14
14
 
@@ -37,7 +37,7 @@ def hfd_matlab_equivalent(data, k_max):
37
37
 
38
38
 
39
39
  @njit
40
- def compute_L_x(X, k_max):
40
+ def compute_L_x(X, k_max=16):
41
41
  N = len(X)
42
42
  L = numpy.zeros(k_max)
43
43
  x = numpy.zeros(k_max)
@@ -55,14 +55,14 @@ def compute_L_x(X, k_max):
55
55
  return x, L
56
56
 
57
57
 
58
- def hfd_pyeeg(X, k_max):
58
+ def hfd_pyeeg(X, k_max=16):
59
59
  x, L = compute_L_x(X, k_max)
60
60
  A = numpy.column_stack((x, numpy.ones_like(x)))
61
61
  beta, _, _, _ = numpy.linalg.lstsq(A, L, rcond=None)
62
62
  return beta[0]
63
63
 
64
64
 
65
- def _hfd_pyeeg(X, k_max):
65
+ def _hfd_pyeeg(X, k_max=16):
66
66
  """
67
67
  [from PyEEG package]
68
68
  Higuchi Fractal Dimension of a time series X. kmax is an HFD parameter
@@ -0,0 +1,201 @@
1
+ #!/Users/donyin/miniconda3/envs/rotation-1/bin/python
2
+
3
+ import numpy
4
+
5
+
6
+ def lempel_ziv(time_series, method="median"):
7
+ """
8
+ - compared to other implementations, this implementation is makes the most sense
9
+
10
+ Lempel-Ziv complexity as described in Kaspar and Schuster, Phys. Rev. A.
11
+ Counts the number of distinct patterns that need to be copied to reproduce the sequence.
12
+ Returns: int: The number of distinct patterns (complexity measure)
13
+ """
14
+ sequence = time_series_to_binary(time_series, method=method)
15
+
16
+ history_pos = 0 # Position in history we're comparing against
17
+ current_len = 1 # Length of current pattern we're checking
18
+ max_pattern_len = 1 # Length of longest matching pattern found
19
+ seq_pos = 1 # Current position in sequence
20
+ complexity = 1 # Start with 1 as first character is always a new pattern
21
+ seq_len = len(sequence)
22
+
23
+ while seq_pos + current_len <= seq_len:
24
+ # Check if current pattern matches what's in history
25
+ if sequence[history_pos + current_len - 1] == sequence[seq_pos + current_len - 1]:
26
+ current_len += 1
27
+ else:
28
+ # Update longest matching pattern length if needed
29
+ if current_len > max_pattern_len:
30
+ max_pattern_len = current_len
31
+
32
+ history_pos += 1
33
+
34
+ # If we've checked all history, we found a new pattern
35
+ if history_pos == seq_pos:
36
+ complexity += 1
37
+ seq_pos += max_pattern_len
38
+
39
+ # Reset for next pattern search
40
+ history_pos = 0
41
+ current_len = 1
42
+ max_pattern_len = 1
43
+ else:
44
+ # Try next position in history
45
+ current_len = 1
46
+
47
+ return complexity
48
+
49
+
50
+ def time_series_to_binary(vector, method="median"):
51
+ """
52
+ - convert a time series to a binary sequence based on a threshold.
53
+ - returns: str: A binary sequence as a string of '0's and '1's.
54
+ - https://doi.org/10.1101/2021.09.23.461002 this paper used median, making median the default method.
55
+ - diff also makes somewhat sense
56
+ """
57
+
58
+ vector = numpy.array(vector)
59
+
60
+ if method == "mean":
61
+ threshold = numpy.mean(vector)
62
+ binary_seq = "".join(["1" if x > threshold else "0" for x in vector])
63
+ elif method == "median":
64
+ threshold = numpy.median(vector)
65
+ binary_seq = "".join(["1" if x > threshold else "0" for x in vector])
66
+ elif method == "diff":
67
+ diffs = numpy.diff(
68
+ vector
69
+ ) # this measures whether the value is increasing or decreasing as compared to the previous value
70
+ binary_seq = "".join(["1" if x > 0 else "0" for x in diffs])
71
+ else:
72
+ raise ValueError("Method must be 'mean', 'median', or 'diff'.")
73
+
74
+ return binary_seq
75
+
76
+
77
+ def lempel_ziv_v1(time_series: numpy.ndarray) -> int:
78
+ r"""Manual implementation of the Lempel-Ziv complexity.
79
+
80
+ It is defined as the number of different substrings encountered as the stream is viewed from begining to the end.
81
+ As an example:
82
+
83
+ >>> s = '1001111011000010'
84
+ >>> lempel_ziv_complexity(s) # 1 / 0 / 01 / 11 / 10 / 110 / 00 / 010
85
+ 8
86
+
87
+ Marking in the different substrings the sequence complexity :math:`\mathrm{Lempel-Ziv}(s) = 8`: :math:`s = 1 / 0 / 01 / 11 / 10 / 110 / 00 / 010`.
88
+
89
+ - See the page https://en.wikipedia.org/wiki/Lempel-Ziv_complexity for more details.
90
+
91
+
92
+ Other examples:
93
+
94
+ >>> lempel_ziv_complexity('1010101010101010') # 1, 0, 10, 101, 01, 010, 1010
95
+ 7
96
+ >>> lempel_ziv_complexity('1001111011000010000010') # 1, 0, 01, 11, 10, 110, 00, 010, 000
97
+ 9
98
+ >>> lempel_ziv_complexity('100111101100001000001010') # 1, 0, 01, 11, 10, 110, 00, 010, 000, 0101
99
+ 10
100
+
101
+ - Note: it is faster to give the sequence as a string of characters, like `'10001001'`, instead of a list or a numpy array.
102
+ - Note: see this notebook for more details, comparison, benchmarks and experiments: https://Nbviewer.Jupyter.org/github/Naereen/Lempel-Ziv_Complexity/Short_study_of_the_Lempel-Ziv_complexity.ipynb
103
+ - Note: there is also a Cython-powered version, for speedup, see :download:`lempel_ziv_complexity_cython.pyx`.
104
+ """
105
+
106
+ sequence = time_series_to_binary(time_series, method="median")
107
+
108
+ sub_strings = set()
109
+
110
+ ind = 0
111
+ inc = 1
112
+ while True:
113
+ if ind + inc > len(sequence):
114
+ break
115
+ sub_str = sequence[ind : ind + inc]
116
+ if sub_str in sub_strings:
117
+ inc += 1
118
+ else:
119
+ sub_strings.add(sub_str)
120
+ ind += inc
121
+ inc = 1
122
+ return len(sub_strings)
123
+
124
+
125
+ def lempel_ziv_v2(time_series):
126
+ """
127
+ Compute the Lempel-Ziv complexity of a binary sequence.
128
+
129
+ Parameters:
130
+ sequence (str): The binary sequence as a string of '0' and '1'.
131
+
132
+ Returns:
133
+ int: The Lempel-Ziv complexity.
134
+ """
135
+ sequence = time_series_to_binary(time_series, method="median")
136
+
137
+ sub_strings = set()
138
+ n = len(sequence)
139
+ ind = 0
140
+ count = 0
141
+
142
+ while ind < n:
143
+ inc = 1
144
+ # Find the shortest substring not already in sub_strings
145
+ while sequence[ind : ind + inc] in sub_strings and ind + inc <= n:
146
+ inc += 1
147
+
148
+ # Add the new substring to the set
149
+ sub_strings.add(sequence[ind : ind + inc])
150
+ count += 1
151
+ ind += inc
152
+
153
+ return count
154
+
155
+
156
+ def lempel_ziv_v4(time_series):
157
+ """
158
+ Computes the Lempel-Ziv complexity of the input sequence s.
159
+
160
+ Parameters:
161
+ s (str): The input sequence (e.g., a binary string).
162
+
163
+ Returns:
164
+ int: The Lempel-Ziv complexity of the sequence.
165
+ """
166
+
167
+ sequence = time_series_to_binary(time_series, method="median")
168
+ n = len(sequence)
169
+ i = 0
170
+ c = 1 # Complexity starts at 1 since the first character is always new
171
+ k = 1
172
+ l = 1
173
+
174
+ while l + k <= n:
175
+ # Check if the substring s[l:l+k] has occurred in s[0:l]
176
+ if sequence[l : l + k] in sequence[0:l]:
177
+ k += 1
178
+ if l + k - 1 == n:
179
+ c += 1
180
+ break
181
+ else:
182
+ c += 1
183
+ l = l + k
184
+ k = 1
185
+
186
+ return c
187
+
188
+
189
+ if __name__ == "__main__":
190
+ sequence = "1001111011000010"
191
+ print(lempel_ziv_v1(sequence))
192
+ print(lempel_ziv_v2(sequence))
193
+ print(lempel_ziv(sequence))
194
+ print(lempel_ziv_v4(sequence))
195
+
196
+ signal = numpy.random.rand(1000)
197
+ binary_sequence = time_series_to_binary(signal, method="median")
198
+ print(lempel_ziv_v1(binary_sequence))
199
+ print(lempel_ziv_v2(binary_sequence))
200
+ print(lempel_ziv(binary_sequence))
201
+ print(lempel_ziv_v4(binary_sequence))
@@ -1,16 +1,20 @@
1
1
  #!/Users/donyin/miniconda3/envs/rotation-1/bin/python
2
2
 
3
+ """
4
+ - validating measures of complexity is actually working using synthetic data on various Hurst exponents
5
+ """
3
6
 
4
7
  import numpy
5
8
  from scipy.fft import fft, ifft
6
9
  import matplotlib.pyplot as plt
7
10
  from donware import inspect_package
8
- from complexeva.measures.higuchi import hfd_pyeeg, hfd_matlab_equivalent
11
+ from complexeva.measures.dimension_1.higuchi import hfd_pyeeg, hfd_matlab_equivalent
12
+ from complexeva.measures.dimension_1.lempel_ziv import lempel_ziv_v1, lempel_ziv_v2, lempel_ziv, lempel_ziv_v4
9
13
 
10
14
 
11
- class HiguchiMethodValidate:
12
- def __init__(self, funcs, k_max: int, timeseries_len=1000, num_hurst=200):
13
- self.funcs, self.k_max, self.num_hurst, self.timeseries_len = funcs, k_max, num_hurst, timeseries_len
15
+ class ComplexityMeasuresValidator2D:
16
+ def __init__(self, funcs, timeseries_len=1000, num_hurst=200):
17
+ self.funcs, self.num_hurst, self.timeseries_len = funcs, num_hurst, timeseries_len
14
18
 
15
19
  def fbm(self, hurst):
16
20
  """
@@ -52,7 +56,7 @@ class HiguchiMethodValidate:
52
56
  hfd_results = []
53
57
 
54
58
  for func in self.funcs:
55
- hfd_results.append([func(series, self.k_max) for series in fBm_series])
59
+ hfd_results.append([func(series) for series in fBm_series])
56
60
 
57
61
  # ---- plotting ----
58
62
  fig, axs = plt.subplots(3, 1, figsize=(9, 10))
@@ -91,6 +95,8 @@ class HiguchiMethodValidate:
91
95
 
92
96
 
93
97
  if __name__ == "__main__":
94
- k_max = 30
95
- validator = HiguchiMethodValidate([hfd_pyeeg, hfd_matlab_equivalent], k_max)
98
+ validator = ComplexityMeasuresValidator2D([hfd_pyeeg, hfd_matlab_equivalent])
99
+ validator.plot_hfd_vs_hurst()
100
+
101
+ validator = ComplexityMeasuresValidator2D([lempel_ziv_v1, lempel_ziv_v2, lempel_ziv, lempel_ziv_v4])
96
102
  validator.plot_hfd_vs_hurst()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: complexeva
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Home-page:
5
5
  Author: Don Yin
6
6
  Author-email: don_yin@outlook.com
@@ -9,11 +9,14 @@ complexeva/dummies/__init__.py
9
9
  complexeva/dummies/matrix.py
10
10
  complexeva/dummies/networks.py
11
11
  complexeva/measures/__init__.py
12
- complexeva/measures/cbb_matrix.py
13
- complexeva/measures/cbb_networkx.py
14
- complexeva/measures/co_activation.py
15
- complexeva/measures/higuchi.py
16
12
  complexeva/measures/validate.py
13
+ complexeva/measures/dimension_1/__init__.py
14
+ complexeva/measures/dimension_1/higuchi.py
15
+ complexeva/measures/dimension_1/lempel_ziv.py
16
+ complexeva/measures/dimension_2/__init__.py
17
+ complexeva/measures/dimension_2/cbb_matrix.py
18
+ complexeva/measures/network_space/__init__.py
19
+ complexeva/measures/network_space/cbb_networkx.py
17
20
  complexeva/utils/__init__.py
18
21
  complexeva/utils/convert_image_matrix.py
19
22
  complexeva/utils/convert_matrix_network.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="complexeva",
5
- version="0.1.5",
5
+ version="0.1.7",
6
6
  packages=find_packages(),
7
7
  install_requires=[],
8
8
  author="Don Yin",
@@ -1,5 +0,0 @@
1
- from .higuchi import hfd_matlab_equivalent, hfd_pyeeg
2
- from .cbb_matrix import FractalHandlerMatrix
3
- from .cbb_networkx import FractalHandlerNetworkX
4
-
5
- __all__ = ["hfd_matlab_equivalent", "hfd_pyeeg", "FractalHandlerMatrix", "FractalHandlerNetworkX"]
@@ -1,100 +0,0 @@
1
- #!/Users/donyin/miniconda3/envs/rotation-1/bin/python
2
-
3
- """
4
- - now we have two 1d series as signals make dummy data using numpy
5
- - use either a sliding window or regression method to produce a new signal that represents the co-activation between the two signals
6
- - in the sliding_window method, the window_size parameter is used to determine the size of the window used to calculate the co-activation
7
- - it moves one step at a time and calculates the correlation between the two signals in the window
8
- - in the regression method, the two signals are used as independent and dependent variables in a linear regression model
9
- - the resulting signal is the predicted dependent variable
10
- - if we use the sliding_window method, the window_size parameter must be an integer greater than 0
11
- - if we use the regression method, we must plot the two signal and the resulting signal
12
- """
13
-
14
- import numpy as np
15
- from sklearn.linear_model import LinearRegression
16
- import matplotlib.pyplot as plt
17
-
18
-
19
- def get_co_activation(
20
- signal_1: np.ndarray, signal_2: np.ndarray, method: str, window_size: int = None, save_plot_as=None
21
- ) -> np.ndarray:
22
- """
23
- Get a measure of co-activation between two 1D signals.
24
-
25
- Args:
26
- signal_1: First 1D time series signal
27
- signal_2: Second 1D time series signal
28
- method: Either 'sliding_window' or 'regression'
29
- window_size: Size of the window for 'sliding_window' method (must be an integer greater than 0)
30
-
31
- Returns:
32
- np.ndarray: A new signal representing the co-activation between the two signals
33
- """
34
- match method:
35
- case "sliding_window":
36
- if window_size is None or window_size <= 0:
37
- raise ValueError("window_size must be an integer greater than 0 for sliding_window method")
38
- correlations = []
39
- num_windows = len(signal_1) - window_size + 1
40
- for i in range(num_windows):
41
- window_signal_1 = signal_1[i : i + window_size]
42
- window_signal_2 = signal_2[i : i + window_size]
43
- corr = np.corrcoef(window_signal_1, window_signal_2)[0, 1]
44
- correlations.append(corr)
45
-
46
- # Plot signals and correlations
47
- correlations = np.array(correlations)
48
- if save_plot_as:
49
- plt.figure(figsize=(10, 5))
50
- plt.plot(signal_1, label="Signal 1", alpha=0.7)
51
- plt.plot(signal_2, label="Signal 2", alpha=0.7)
52
- plt.plot(
53
- np.arange(window_size // 2, len(correlations) + window_size // 2),
54
- correlations,
55
- label="Sliding Window Correlation",
56
- linestyle="--",
57
- )
58
- plt.legend()
59
- plt.title(f"Sliding Window Method Co-Activation (window size={window_size})")
60
- plt.savefig(save_plot_as, dpi=320)
61
- plt.close()
62
-
63
- return correlations
64
-
65
- case "regression":
66
- # Reshape signals for regression
67
- X = signal_1.reshape(-1, 1)
68
- y = signal_2
69
-
70
- # Fit linear regression
71
- model = LinearRegression()
72
- model.fit(X, y)
73
-
74
- # Predict y
75
- y_pred = model.predict(X)
76
-
77
- # Plot signals
78
- if save_plot_as:
79
- plt.figure(figsize=(10, 5))
80
- plt.plot(signal_1, label="Signal 1 (IV)")
81
- plt.plot(signal_2, label="Signal 2 (DV)")
82
- plt.plot(y_pred, label="Predicted DV", linestyle="--")
83
- plt.legend()
84
- plt.title("Regression Method Co-Activation")
85
- plt.savefig(save_plot_as, dpi=320)
86
- plt.close()
87
-
88
- return y_pred
89
-
90
- case _:
91
- raise ValueError("method must be 'sliding_window' or 'regression'")
92
-
93
-
94
- if __name__ == "__main__":
95
- t = np.linspace(0, 10, 1000)
96
- signal1 = np.sin(2 * np.pi * 0.5 * t) + 0.2 * np.random.randn(1000)
97
- signal2 = 0.8 * np.sin(2 * np.pi * 0.5 * t + np.pi / 4) + 0.2 * np.random.randn(1000)
98
-
99
- get_co_activation(signal1, signal2, method="sliding_window", window_size=32, save_plot_as="coactivation_sliding_window.png")
100
- get_co_activation(signal1, signal2, method="regression", save_plot_as="coactivation_regression.png")
File without changes
File without changes