Coverage for src/foapy/ma/_intervals.py: 99%
68 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-17 20:45 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-17 20:45 +0000
1import numpy as np
2from numpy import ma
4from foapy import binding, mode
5from foapy.exceptions import InconsistentOrderException, Not1DArrayException
8def intervals(X, bind, mod):
9 """
10 Finding array of array of intervals of the uniform
11 sequences in the given input sequence
13 Parameters
14 ----------
15 X: masked_array
16 Array to get intervals.
18 binding: int
19 binding.start = 1 - Intervals are extracted from left to right.
20 binding.end = 2 – Intervals are extracted from right to left.
22 mode: int
23 mode.lossy = 1 - Both interval from the start of the sequence
24 to the first element occurrence and interval from the
25 last element occurrence to the end of the sequence are not taken into account.
27 mode.normal = 2 - Interval from the start of the sequence to the
28 first occurrence of the element or interval from the last occurrence
29 of the element to the end of the sequence is taken into account.
31 mode.cycle = 3 - Interval from the start of the sequence to the first
32 element occurrence
33 and interval from the last element occurrence to the end of the
34 sequence are summed
35 into one interval (as if sequence was cyclic). Interval is
36 placed either in the
37 beginning of intervals array (in case of binding to the
38 beginning) or in the end.
40 mode.redundant = 4 - Both interval from start of the sequence
41 to the first element
42 occurrence and the interval from the last element occurrence
43 to the end of the
44 sequence are taken into account. Their placement in results
45 array is determined
46 by the binding.
48 Returns
49 -------
50 result: array or Exception.
51 Exception if not d1 array or wrong mask, array otherwise.
53 Examples
54 --------
56 ----1----
57 >>> import foapy.ma as ma
58 >>> a = [2, 4, 2, 2, 4]
59 >>> b = ma.intervals(X, binding.start, mode.lossy)
60 >>> b
61 [
62 [5],
63 [1, 4],
64 [],
65 []
66 ]
68 ----2----
69 >>> import foapy.ma as ma
70 >>> a = [2, 4, 2, 2, 4]
71 >>> b = ma.intervals(X, binding.end, mode.lossy)
72 >>> b
73 [
74 [5],
75 [1, 4],
76 [],
77 []
78 ]
80 ----3----
81 >>> import foapy.ma as ma
82 >>> a = [2, 4, 2, 2, 4]
83 >>> b = ma.intervals(X, binding.start, mode.normal)
84 >>> b
85 [
86 [1, 2, 1],
87 [2, 3]
88 ]
90 ----4----
91 >>> import foapy.ma as ma
92 >>> a = [2, 4, 2, 2, 4]
93 >>> b = ma.intervals(X, binding.end, mode.normal)
94 >>> b
95 [
96 [2, 1, 2],
97 [3, 1]
98 ]
100 ----5----
101 >>> import foapy.ma as ma
102 >>> a = [2, 4, 2, 2, 4]
103 >>> b = ma.intervals(X, binding.start, mode.cycle)
104 >>> b
105 [
106 [2, 2, 1],
107 [2, 3]
108 ]
110 ----6----
111 >>> import foapy.ma as ma
112 >>> a = [2, 4, 2, 2, 4]
113 >>> b = ma.intervals(X, binding.end, mode.cycle)
114 >>> b
115 [
116 [2, 1, 2],
117 [3, 2]
118 ]
120 ----7----
121 >>> import foapy.ma as ma
122 >>> a = [2, 4, 2, 2, 4]
123 >>> b = ma.intervals(X, binding.start, mode.redunant)
124 >>> b
125 [
126 [1, 2, 1, 2],
127 [2, 3, 1]
128 ]
130 ----8----
131 >>> import foapy.ma as ma
132 >>> a = [2, 4, 2, 2, 4]
133 >>> b = ma.intervals(X, binding.end, mode.redunant)
134 >>> b
135 [
136 [1, 2, 1, 2],
137 [2, 3, 1]
138 ]
140 ----9----
141 >>> import foapy.ma as ma
142 >>> a = ['a', 'b', 'c', 'a', 'b', 'c', 'c', 'c', 'b', 'a', 'c', 'b', 'c']
143 >>> mask = [0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0]
144 >>> masked_a = ma.masked_array(a, mask)
145 >>> b = intervals(X, binding.end, mode.redunant)
146 >>> b
147 Exception
149 ----10----
150 >>> import foapy.ma as ma
151 >>> a = [[2, 2, 2], [2, 2, 2]]
152 >>> mask = [[0, 0, 0], [0, 0, 0]]
153 >>> masked_a = ma.masked_array(a, mask)
154 >>> b = ma.intervals(X, binding.end, mode.redunant)
155 >>> b
156 Exception
157 """
159 # Validate binding
160 if bind not in {binding.start, binding.end}:
161 raise ValueError(
162 {"message": "Invalid binding value. Use binding.start or binding.end."}
163 )
165 # Validate mode
166 if mod not in {mode.lossy, mode.normal, mode.cycle, mode.redundant}:
167 raise ValueError(
168 {"message": "Invalid mode value. Use mode.lossy,normal,cycle or redundant."}
169 )
170 # ex.:
171 # ar = ['a', 'c', 'c', 'e', 'd', 'a']
173 power = X.shape[0]
174 if power == 0:
175 return np.array([])
177 if len(X.shape) != 2:
178 message = f"Incorrect array form. Expected d2 array, exists {len(X.shape)}"
179 raise Not1DArrayException({"message": message})
181 length = X.shape[1]
183 mask = ma.getmaskarray(X)
185 verify_data = X[~mask]
186 verify_positions = np.transpose(np.argwhere(~mask))
187 verify = np.empty(verify_data.shape[0], dtype=bool)
188 verify[:1] = False
189 verify[1:] = np.logical_xor(
190 verify_positions[0, 1:] != verify_positions[0, :-1],
191 verify_data[1:] != verify_data[:-1],
192 )
194 if not np.all(~verify):
195 failed_indexes = verify_positions[0, verify]
196 i = X[failed_indexes[0]]
197 raise InconsistentOrderException(
198 {"message": f"Elements {i} have wrong appearance"}
199 )
201 extended_mask = np.empty((power, length + 1), dtype=bool)
202 if bind == binding.end:
203 extended_mask[:, :-1] = ~mask[::-1, ::-1]
204 else:
205 extended_mask[:, :-1] = ~mask
206 extended_mask[:, -1] = np.any(extended_mask[:, :-1], axis=1)
208 positions = np.transpose(np.argwhere(extended_mask))
210 border_indexes = np.empty(positions.shape[1] + 1, dtype=bool)
211 border_indexes[:1] = True
212 border_indexes[1:-1] = positions[0, 1:] != positions[0, :-1]
213 border_indexes[-1:] = True
215 first_indexes = np.argwhere(border_indexes[:-1]).ravel()
216 last_indexes = np.argwhere(border_indexes[1:]).ravel()
218 indecies = np.zeros(positions.shape[1], dtype=int)
219 indecies[1:] = positions[1, 1:] - positions[1, :-1]
220 delta = indecies[last_indexes] if mod == mode.cycle else 1
221 indecies[first_indexes] = positions[1][first_indexes] + delta
223 split_boarders = np.zeros(power * 2, dtype=int)
225 if mod == mode.lossy:
226 split_boarders[positions[0][last_indexes[:1]] * 2] = first_indexes[:1] + 1
227 split_boarders[positions[0][last_indexes[1:]] * 2] = (
228 np.fmax(last_indexes[:-1], first_indexes[1:]) + 1
229 )
230 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes
231 elif mod == mode.normal:
232 split_boarders[positions[0][last_indexes[:1]] * 2] = 0
233 split_boarders[positions[0][last_indexes[1:]] * 2] = last_indexes[:-1] + 1
234 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes
235 elif mod == mode.cycle:
236 split_boarders[positions[0][last_indexes[:1]] * 2] = 0
237 split_boarders[positions[0][last_indexes[1:]] * 2] = last_indexes[:-1] + 1
238 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes
239 elif mod == mode.redundant: 239 ↛ 244line 239 didn't jump to line 244 because the condition on line 239 was always true
240 split_boarders[positions[0][last_indexes[:1]] * 2] = 0
241 split_boarders[positions[0][last_indexes[1:]] * 2] = 0
242 split_boarders[positions[0][last_indexes] * 2 + 1] = last_indexes + 1
244 preserve_previous = np.frompyfunc(lambda x, y: x if y == 0 else y, 2, 1)
245 split_boarders = preserve_previous.accumulate(split_boarders)
246 if bind == binding.end:
247 split_boarders[:-1] = np.diff(split_boarders)
248 split_boarders[-1:] = len(indecies) - split_boarders[-1]
249 split_boarders = np.cumsum(split_boarders[::-1])
250 indecies = indecies[::-1]
252 indecies = np.array_split(indecies, split_boarders)
253 return indecies[1:-1:2]