proadv 2.0.2__py3-none-any.whl

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 (34) hide show
  1. proadv/__init__.py +0 -0
  2. proadv/filtration/__init__.py +0 -0
  3. proadv/filtration/detection/__init__.py +0 -0
  4. proadv/filtration/detection/acceleration.py +65 -0
  5. proadv/filtration/detection/bivariatekernel.py +53 -0
  6. proadv/filtration/detection/correlation.py +78 -0
  7. proadv/filtration/detection/phasespace.py +173 -0
  8. proadv/filtration/detection/poincare.py +46 -0
  9. proadv/filtration/detection/pollution.py +32 -0
  10. proadv/filtration/detection/spherical.py +181 -0
  11. proadv/filtration/detection/trivariatekernel.py +521 -0
  12. proadv/filtration/replacement/__init__.py +0 -0
  13. proadv/filtration/replacement/replacements.py +187 -0
  14. proadv/kernel/__init__.py +0 -0
  15. proadv/kernel/bivariate.py +437 -0
  16. proadv/statistics/__init__.py +0 -0
  17. proadv/statistics/descriptive.py +328 -0
  18. proadv/statistics/distributions/__init__.py +1 -0
  19. proadv/statistics/distributions/normal.py +126 -0
  20. proadv/statistics/moment.py +122 -0
  21. proadv/statistics/series.py +337 -0
  22. proadv/statistics/spread.py +119 -0
  23. proadv/tests/__init__.py +0 -0
  24. proadv/tests/statistics/__init__.py +0 -0
  25. proadv/tests/statistics/descriptive/__init__.py +0 -0
  26. proadv/tests/statistics/descriptive/test_max.py +36 -0
  27. proadv/tests/statistics/descriptive/test_mean.py +37 -0
  28. proadv/tests/statistics/descriptive/test_median.py +26 -0
  29. proadv/tests/statistics/descriptive/test_min.py +36 -0
  30. proadv/tests/statistics/descriptive/test_mode.py +34 -0
  31. proadv-2.0.2.dist-info/METADATA +167 -0
  32. proadv-2.0.2.dist-info/RECORD +34 -0
  33. proadv-2.0.2.dist-info/WHEEL +5 -0
  34. proadv-2.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,521 @@
1
+ import numpy as np
2
+ from scipy import linalg
3
+ from scipy.stats._stats import gaussian_kernel_estimate as gke
4
+ from scipy.stats import gaussian_kde as kde
5
+ from proadv.statistics.spread import std
6
+
7
+
8
+ def _derivatives(data):
9
+ """
10
+ Calculate time-independent first and second order derivatives of the input data.
11
+
12
+ Parameters
13
+ ------
14
+ data (array_like): Input data.
15
+
16
+ Returns
17
+ ------
18
+ dc (array_like): First derivative of the input data.
19
+ dc2 (array_like): Second derivative of the input data.
20
+ """
21
+
22
+ # Initialize arrays for first and second derivatives
23
+ dc = np.zeros_like(data)
24
+ dc2 = np.zeros_like(data)
25
+
26
+ # Calculate first derivative
27
+ for i in range(1, data.size - 1):
28
+ dc[i] = (data[i + 1] - data[i - 1]) / 2
29
+
30
+ # Calculate second derivative
31
+ for i in range(1, data.size - 1):
32
+ dc2[i] = (dc[i + 1] - dc[i - 1]) / 2
33
+
34
+ return dc, dc2
35
+
36
+
37
+ def _rotation(u1, w1):
38
+ """
39
+ Calculate rotation angle theta between vectors u1 and w1.
40
+
41
+ Parameters
42
+ ------
43
+ u1 (array_like): Vector u1.
44
+ w1 (array_like): Vector w1.
45
+
46
+ Returns
47
+ ------
48
+ theta (float): Rotation angle.
49
+ """
50
+
51
+ # Compute data size
52
+ data_size = u1.size
53
+
54
+ # Calculate theta using the arctan2 function
55
+ theta = np.arctan2((data_size * np.sum(u1 * w1) - np.sum(u1) * np.sum(w1)),
56
+ (data_size * np.sum(u1 * u1) - np.sum(u1) * np.sum(u1)))
57
+
58
+ return theta
59
+
60
+
61
+ def _transform(x, y, theta):
62
+ """
63
+ Transform coordinates (x, y) using rotation angle theta.
64
+
65
+ Parameters
66
+ ------
67
+ x (array_like): x-coordinate.
68
+ y (array_like): y-coordinate.
69
+ theta (float): Rotation angle
70
+
71
+ Returns
72
+ ------
73
+ xt, yt (array_like): Transformed coordinates.
74
+ """
75
+
76
+ # Apply rotation transformation
77
+ xt = x * np.cos(theta) + y * np.sin(theta)
78
+ yt = -x * np.sin(theta) + y * np.cos(theta)
79
+
80
+ return xt, yt
81
+
82
+
83
+ def _scaling(x, y, grid):
84
+ """
85
+ Scale coordinates (x, y) into a grid.
86
+
87
+ Parameters
88
+ ------
89
+ x (array_like): x-coordinate.
90
+ y (array_like): y-coordinate.
91
+ grid (int): Number of grid points.
92
+
93
+ Returns
94
+ ------
95
+ Meshgrid of scaled coordinates.
96
+ """
97
+
98
+ xmin = x.min()
99
+ xmax = x.max()
100
+ ymin = y.min()
101
+ ymax = y.max()
102
+
103
+ # Generate a meshgrid of scaled coordinates
104
+ return np.mgrid[xmin:xmax:grid * 1j, ymin:ymax:grid * 1j]
105
+
106
+
107
+ def _profile(meshgrid_x, meshgrid_y):
108
+ """
109
+ Extract profile coordinates from meshgrid.
110
+
111
+ Parameters
112
+ ------
113
+ meshgrid_x (array_like): Meshgrid of x-coordinates.
114
+ meshgrid_y (array_like): Meshgrid of y-coordinates.
115
+
116
+ Returns
117
+ ------
118
+ Profile coordinates.
119
+ """
120
+
121
+ # Extract profile coordinates from meshgrid
122
+ return meshgrid_x[:, 0], meshgrid_y[0, :]
123
+
124
+
125
+ def _position(meshgrid_x, meshgrid_y, trans_x, trans_y):
126
+ """
127
+ Generate positions and values for interpolation.
128
+
129
+ Parameters
130
+ ------
131
+ meshgrid_x (array_like): Meshgrid of x-coordinates.
132
+ meshgrid_y (array_like): Meshgrid of y-coordinates.
133
+ trans_x (array_like): Transformed x-coordinates.
134
+ trans_y (array_like): Transformed y-coordinates.
135
+
136
+ Returns
137
+ ------
138
+ Positions and values for interpolation.
139
+ """
140
+
141
+ # Stack meshgrid coordinates and transformed coordinates
142
+ positions = np.vstack([meshgrid_x.ravel(), meshgrid_y.ravel()])
143
+ values = np.vstack([trans_x, trans_y])
144
+
145
+ return positions, values
146
+
147
+
148
+ def _factor(rows, cols):
149
+ """
150
+ Calculate covariance factor based on rows and columns.
151
+
152
+ Parameters
153
+ ------
154
+ rows (int): Number of rows.
155
+ cols (int): Number of columns.
156
+
157
+ Returns
158
+ ------
159
+ Factor.
160
+ """
161
+
162
+ # Calculate the covariance factor
163
+ return np.power(cols, -1. / (rows + 4))
164
+
165
+
166
+ def _weight(cols):
167
+ """
168
+ Generate weights.
169
+
170
+ Parameters
171
+ ------
172
+ cols (int): Number of columns.
173
+
174
+ Returns
175
+ ------
176
+ Array of weights.
177
+ """
178
+
179
+ # Generate equal weights
180
+ return np.ones(cols) / cols
181
+
182
+
183
+ def _cov(data, aweights):
184
+ """
185
+ Compute the weighted covariance matrix.
186
+
187
+ Parameters
188
+ ------
189
+ data (array_like): Input data.
190
+ aweights (array_like): Array of weights.
191
+
192
+ Returns
193
+ ------
194
+ Weighted covariance matrix.
195
+ """
196
+
197
+ # Compute the weighted covariance matrix
198
+ return np.atleast_2d(np.cov(data, rowvar=1, bias=False, aweights=aweights))
199
+
200
+
201
+ def _cholesky(data):
202
+ """
203
+ Compute the Cholesky decomposition of a matrix.
204
+
205
+ Parameters
206
+ ------
207
+ data (array_like): Input matrix.
208
+
209
+ Returns
210
+ ------
211
+ Cholesky decomposition of the input matrix.
212
+ """
213
+
214
+ # Compute the Cholesky decomposition
215
+ return linalg.cholesky(data)
216
+
217
+
218
+ def _determination(data):
219
+ """
220
+ Compute the determinant of a matrix.
221
+
222
+ Parameters
223
+ ------
224
+ data (array_like): Input matrix.
225
+
226
+ Returns
227
+ ------
228
+ Determinant of the input matrix.
229
+ """
230
+
231
+ # Compute the determinant
232
+ return 2 * np.sum(np.log(np.diag(data * np.sqrt(np.pi * 2))))
233
+
234
+
235
+ def _covariance(data, rows, cols):
236
+ """
237
+ Calculate the covariance and Cholesky decomposition.
238
+
239
+ Parameters
240
+ ------
241
+ data (array_like): Input data
242
+ rows (int): Number of rows.
243
+ cols (int): Number of columns.
244
+
245
+ Returns
246
+ ------
247
+ Dictionary containing computed factors, weights, net, covariance, Cholesky decomposition, and log.
248
+ """
249
+
250
+ # Compute factors and weights
251
+ weight = _weight(cols)
252
+
253
+ # Compute net and adjust factor
254
+ net = np.power(np.sum(weight ** 2), -1)
255
+ factor = _factor(rows, net)
256
+
257
+ # Compute covariance matrix and its Cholesky decomposition
258
+ cov = _cov(data, weight)
259
+ sky = _cholesky(cov)
260
+
261
+ # Adjust covariance and Cholesky decomposition
262
+ covariance = cov * factor ** 2
263
+ cholesky = sky * factor
264
+ cholesky.dtype = np.float64
265
+
266
+ # Compute log
267
+ log = _determination(cholesky)
268
+
269
+ # Return computed values as a dictionary
270
+ compute = {
271
+ "factor": factor,
272
+ "weight": weight,
273
+ "net": net,
274
+ "covariance": covariance,
275
+ "cholesky": cholesky,
276
+ "log": log
277
+ }
278
+
279
+ return compute
280
+
281
+
282
+ def _type(cov, scatt):
283
+ """
284
+ Determine the data type and size.
285
+
286
+ Parameters
287
+ ------
288
+ cov (array_like): Covariance matrix.
289
+ scatt (array_like): Scattering data.
290
+
291
+ Returns
292
+ ------
293
+ Data type and size.
294
+ """
295
+
296
+ data_type = np.common_type(cov, scatt)
297
+ data_size = np.dtype(data_type).itemsize
298
+ if data_size == 4:
299
+ data_size = 'float'
300
+ elif data_size == 8:
301
+ data_size = 'double'
302
+ elif data_size in (12, 16):
303
+ data_size = 'long double'
304
+ return data_type, data_size
305
+
306
+
307
+ def _density(values):
308
+ """
309
+ Compute the density estimation.
310
+
311
+ Parameters
312
+ ------
313
+ values (array_like): Values for density estimation.
314
+
315
+ Returns
316
+ ------
317
+ Dictionary containing the density estimation results.
318
+ """
319
+
320
+ dataset = np.atleast_2d(np.asarray(values))
321
+ if dataset.size < 1:
322
+ raise ValueError("Dataset should have more than one element.")
323
+ rows, cols = dataset.shape
324
+ if rows > cols:
325
+ raise ValueError("Number of dimensions exceeds the number of samples.")
326
+ evals = _covariance(dataset, rows, cols)
327
+ return evals
328
+
329
+
330
+ def _estimation(krn, x):
331
+ """
332
+ Perform density estimation.
333
+
334
+ Parameters
335
+ ------
336
+ krn (array_like): Kernel density estimation.
337
+ x (array_like): Input data.
338
+
339
+ Returns
340
+ ------
341
+ Estimated density.
342
+ """
343
+ return np.reshape(krn, x.shape)
344
+
345
+
346
+ def _extraction(dataset, parameters, poses):
347
+ """
348
+ Extract necessary data for extraction.
349
+
350
+ Parameters
351
+ ------
352
+ dataset (array_like): Input dataset.
353
+ parameters (dict): Parameters dictionary containing covariance and scattering data.
354
+ poses (array_like): Scattering positions.
355
+
356
+ Returns
357
+ ------
358
+ Extracted dataset components.
359
+ """
360
+ scatt = np.atleast_2d(np.asarray(poses))
361
+ data_type, data_mode = _type(parameters["covariance"], scatt)
362
+ return dataset.T, parameters["weight"][:, None], poses.T, parameters["cholesky"].T, data_type, data_mode
363
+
364
+
365
+ def _evolve(dataset, poses, computations):
366
+ """
367
+ Evolve the dataset based on computed parameters.
368
+
369
+ Parameters
370
+ ------
371
+ dataset (array_like): Input dataset.
372
+ poses (array_like): Scattering poses.
373
+ computations (dict): Computed parameters.
374
+
375
+ Returns
376
+ ------
377
+ Evolved dataset.
378
+ """
379
+ dataset = _extraction(dataset, computations, poses)
380
+ dens = gke[dataset[5]](
381
+ dataset[0],
382
+ dataset[1],
383
+ dataset[2],
384
+ dataset[3],
385
+ dataset[4])
386
+ return dens[:, 0]
387
+
388
+
389
+ def _peak(pdf):
390
+ """
391
+ Find the peak in the probability density function.
392
+
393
+ Parameters
394
+ ------
395
+ pdf (array_like): Probability density function.
396
+
397
+ Returns
398
+ ------
399
+ Peak and its indices.
400
+ """
401
+ peak = pdf.max()
402
+ up, wp = np.where(pdf == peak)[0][0], np.where(pdf == peak)[1][0]
403
+ fu = pdf[:, wp]
404
+ fw = pdf[up, :]
405
+ return peak, up, wp, fu, fw
406
+
407
+
408
+ def _cutoff(density_profile, velocity_profile, c1_threshold, c2_threshold, force_profile, peak_index, grid):
409
+ """
410
+ Find the lower and upper cutoff velocities based on specified criteria.
411
+
412
+ Parameters
413
+ ------
414
+ density_profile (array_like): Density profile of the system.
415
+ velocity_profile (array_like): Velocity profile of the system.
416
+ c1_threshold (float): Threshold ratio for force compared to peak force.
417
+ c2_threshold (float): Threshold for absolute change in force.
418
+ force_profile (array_like): Force profile of the system.
419
+ peak_index (int): Index of the peak force in the force profile.
420
+ grid (int): Grid spacing or resolution.
421
+
422
+ Returns
423
+ ------
424
+ lower_cutoff_velocity, upper_cutoff_velocity: A tuple containing the lower and upper cutoff velocities.
425
+
426
+ Note
427
+ ------
428
+ This function assumes that the input arrays (density_profile, velocity_profile, and force_profile)
429
+ are of the same length.
430
+ length.
431
+ The density profile, velocity profile, and force profile should be consistent and correspond to
432
+ each other at each index.
433
+ """
434
+
435
+ profile_length = force_profile.size
436
+ delta_force = np.append([0], np.diff(force_profile)) * grid / density_profile
437
+
438
+ # Find lower cutoff index
439
+ for i in range(peak_index - 1, 0, -1):
440
+ if force_profile[i] / force_profile[peak_index] <= c1_threshold and abs(delta_force[i]) <= c2_threshold:
441
+ lower_cutoff_index = i
442
+ break
443
+ else:
444
+ lower_cutoff_index = 1
445
+
446
+ # Find upper cutoff index
447
+ for i in range(peak_index + 1, profile_length - 1):
448
+ if force_profile[i] / force_profile[peak_index] <= c1_threshold and abs(delta_force[i]) <= c2_threshold:
449
+ upper_cutoff_index = i
450
+ break
451
+ else:
452
+ upper_cutoff_index = profile_length - 1
453
+
454
+ lower_cutoff_velocity = velocity_profile[lower_cutoff_index]
455
+ upper_cutoff_velocity = velocity_profile[upper_cutoff_index]
456
+
457
+ return lower_cutoff_velocity, upper_cutoff_velocity
458
+
459
+
460
+ def kernel(u1, w1, grid):
461
+ """
462
+ Perform kernel computation.
463
+
464
+ Parameters
465
+ ------
466
+ u1 (array_like): Input velocity component.
467
+ w1 (array_like): Input velocity component.
468
+ grid (int): Grid size.
469
+
470
+ Returns
471
+ ------
472
+ Spieks Indices and estimation.
473
+ """
474
+
475
+ dataset = np.array([u1, w1])
476
+ theta = _rotation(u1, w1)
477
+ ut, wt = _transform(u1, w1, theta)
478
+ mesh_u, mesh_w = _scaling(ut, wt, grid)
479
+ uf, wf = _profile(mesh_u, mesh_w)
480
+ positions, values = _position(mesh_u, mesh_w, ut, wt)
481
+ evals = _density(values)
482
+ evole = _evolve(dataset, positions, evals)
483
+ estimation = _estimation(evole.T, mesh_u)
484
+ density = np.reshape(kde(values)(positions).T, mesh_u.shape)
485
+ peak, up, wp, fu, fw = _peak(density)
486
+ lambda_ = np.sqrt(2 * np.log(u1.size))
487
+ stdu = std(u1)
488
+ stdw = std(w1)
489
+ cu = lambda_ * stdu / np.sqrt(u1.size)
490
+ cw = lambda_ * stdw / np.sqrt(u1.size)
491
+ ul, uu = _cutoff(peak, uf, cu, cu, fu, up, grid)
492
+ wl, wu = _cutoff(peak, wf, cw, cw, fw, wp, grid)
493
+ uu1 = uu - 0.5 * (uu + ul)
494
+ wu1 = wu - 0.5 * (wu + wl)
495
+ Ut1 = ut - 0.5 * (uu + ul)
496
+ Wt1 = wt - 0.5 * (wu + wl)
497
+ rho = (Ut1 / uu1) ** 2 + (Wt1 / wu1) ** 2
498
+ id_ = np.where(rho > 1)[0]
499
+
500
+ return id_, estimation
501
+
502
+
503
+ def three_dimensional_kernel(data, grid):
504
+ """
505
+ Perform three-dimensional kernel computation.
506
+
507
+ Parameters
508
+ ------
509
+ data (array_like): Input data.
510
+ grid (int): Grid size.
511
+
512
+ Returns
513
+ ------
514
+ Spikes indices.
515
+ """
516
+ dc, dc2 = _derivatives(data)
517
+ x1, _ = kernel(data, dc, grid)
518
+ x2, _ = kernel(data, dc2, grid)
519
+ x3, _ = kernel(dc, dc2, grid)
520
+ kde3d_indices = np.sort(np.unique(np.concatenate((x1, x2, x3))))
521
+ return kde3d_indices
File without changes
@@ -0,0 +1,187 @@
1
+ import numpy as np
2
+ from proadv.statistics.descriptive import mean
3
+ from scipy.interpolate import interp1d
4
+
5
+
6
+ def last_valid_data(velocities, spike_indices):
7
+ """
8
+ Replace spike values in velocities array with the last valid data before each.
9
+
10
+ Parameters
11
+ ------
12
+ velocities (array_like): Array of velocity data.
13
+ An array-like object containing velocity values.
14
+ spike_indices (array_like): Indices of spikes.
15
+ An array-like object containing the indices of detected spikes.
16
+
17
+ Returns
18
+ ------
19
+ modified_data (array_like): Modified data with spikes replaced by last valid values.
20
+ An array containing the modified data.
21
+ """
22
+ # Create a copy of the original data
23
+ modified_data = np.copy(velocities)
24
+
25
+ # Replace values at spikes indices with the last valid value
26
+ for idx in spike_indices:
27
+ modified_data[idx] = modified_data[idx - 1]
28
+
29
+ return modified_data
30
+
31
+
32
+ def mean_value(velocities, spike_indices):
33
+ """
34
+ Replace spike values in velocities array with the mean value of velocity component.
35
+
36
+ Parameters
37
+ ------
38
+ velocities (array_like): Array of velocity data.
39
+ An array-like object containing velocity values.
40
+ spike_indices (array_like): Indices of spikes.
41
+ An array-like object containing the indices of detected spikes.
42
+
43
+ Returns
44
+ ------
45
+ modified_data (array_like): Modified data with spikes replaced by mean value of velocity component.
46
+ An array containing the modified data.
47
+ """
48
+ # Create a copy of the original data
49
+ modified_data = np.copy(velocities)
50
+
51
+ # Replace values at spikes indices with the mean value
52
+ modified_data[spike_indices] = mean(velocities)
53
+
54
+ return modified_data
55
+
56
+
57
+ def linear_interpolation(velocities, spike_indices, decimals=4):
58
+ """
59
+ Perform linear interpolation to replace spike values in velocity data.
60
+
61
+ This function identifies spikes in the velocity data and replaces them with
62
+ linearly interpolated values based on the nearest valid data points before
63
+ and after each spike. If a spike occurs at the end of the data, it is replaced
64
+ with the mean of the entire dataset. The function is robust to handle individual
65
+ spikes as well as sequences of consecutive spikes.
66
+
67
+ Parameters
68
+ ------
69
+ velocities : (array_like)
70
+ An array-like object containing velocity values. It should be a one-dimensional
71
+ array of numerical data representing velocities.
72
+ spike_indices : (array_like)
73
+ An array-like object containing the indices of detected spike events. It should
74
+ be a one-dimensional array of integers where each integer represents the index
75
+ in 'velocities' that corresponds to a spike.
76
+ decimals : int, optional
77
+ The number of decimal places to round the interpolated values to. This allows
78
+ the output data to be presented with a consistent level of precision. The default
79
+ value is 4, but this can be adjusted as needed.
80
+
81
+ Returns
82
+ ------
83
+ modified_data (array_like):
84
+ An array containing the modified velocity data with spikes replaced by
85
+ interpolated values. The shape and type of the array are the same as the
86
+ input 'velocities' array.
87
+
88
+ Notes
89
+ ------
90
+ The function uses linear interpolation to estimate the values of spikes based on the
91
+ surrounding non-spike data. For spikes at the beginning or end of the data where a
92
+ neighboring non-spike value is not available, the function uses the mean of the entire
93
+ dataset as a replacement value. This approach ensures that the data remains as accurate
94
+ and representative of the original dataset as possible.
95
+
96
+ """
97
+
98
+ # Create a copy of the velocity data to avoid modifying the original array
99
+ modified_data = np.copy(velocities)
100
+
101
+ # Initialize spike values with NaN to facilitate interpolation
102
+ modified_data[spike_indices] = np.nan
103
+
104
+ # Calculate differences between consecutive spike indices
105
+ spike_diff = np.diff(spike_indices)
106
+ # Identify the start index of each spike sequence
107
+ spike_starts = spike_indices[np.insert(spike_diff > 1, 0, True)]
108
+ # Identify the end index of each spike sequence
109
+ spike_ends = spike_indices[np.append(spike_diff > 1, True)]
110
+
111
+ # Iterate over each spike sequence for interpolation
112
+ for start, end in zip(spike_starts, spike_ends):
113
+ # Find the valid data point immediately before the spike sequence
114
+ valid_start = modified_data[start - 1] if start > 0 else np.nan
115
+ # Find the valid data point immediately after the spike sequence
116
+ valid_end = modified_data[end + 1] if end < len(velocities) - 1 else np.nan
117
+
118
+ # If valid start and end points are found, perform linear interpolation
119
+ if not np.isnan(valid_start) and not np.isnan(valid_end):
120
+ # Calculate the interpolation step size
121
+ step = (valid_end - valid_start) / (end - start + 2)
122
+ # Apply linear interpolation across the spike sequence
123
+ for i, idx in enumerate(range(start, end + 1)):
124
+ modified_data[idx] = valid_start + step * (i + 1)
125
+ elif np.isnan(valid_end): # Handle spike sequences at the end of the data
126
+ # Replace end spikes with the mean of non-NaN values in the dataset
127
+ modified_data[start:end + 1] = mean(velocities[~np.isnan(velocities)])
128
+ else:
129
+ # Use the mean of valid start and end points as a fallback
130
+ fallback_value = np.nanmean([valid_start, valid_end])
131
+ modified_data[start:end + 1] = fallback_value
132
+
133
+ # Round the interpolated values to the specified number of decimal places
134
+ return np.around(modified_data, decimals=decimals)
135
+
136
+
137
+ def cubic_12points_polynomial(velocities, spike_indices, decimals=4):
138
+ """
139
+ Interpolates missing data points in a velocity array using cubic polynomial interpolation.
140
+
141
+ Parameters
142
+ ------
143
+ velocities (array_like): Array of velocity data. It should be a one-dimensional array-like object.
144
+ This function assumes the input velocities array has at least 25 data points.
145
+ spike_indices (array_like): Indices where data is missing (spikes). It should be a one-dimensional array-like
146
+ object containing integers representing the indices of missing data points (spikes).
147
+ If spike_indices contains invalid indices (e.g., negative values or indices exceeding the array size),
148
+ a ValueError will be raised.
149
+ decimals (int, optional): Number of decimal places to round the result to. Default is 4.
150
+
151
+ Returns
152
+ ------
153
+ modified_data (array_like): Array of velocities with missing data interpolated. The interpolated values
154
+ are calculated based on cubic polynomial interpolation using neighboring data points.
155
+
156
+ Raises
157
+ ------
158
+ ValueError: If spike_indices contains invalid indices or if velocities is not a one-dimensional array-like object.
159
+
160
+ Notes
161
+ ------
162
+ - This function uses cubic polynomial interpolation to estimate missing data points based on neighboring values.
163
+ - If a missing data point (spike) occurs near the boundaries of the velocities array, linear interpolation
164
+ is used instead of cubic polynomial interpolation.
165
+ - The input velocities array is modified in-place to replace missing data points with interpolated values.
166
+ """
167
+
168
+ # Make a copy of velocities to preserve original data
169
+ modified_data = velocities.copy()
170
+
171
+ # Replace spike indices with NaN values
172
+ modified_data[spike_indices] = np.nan
173
+
174
+ # Generate x values for interpolation
175
+ x = np.array(list(range(1, 13)) + list(range(14, 26)))
176
+
177
+ for i in spike_indices:
178
+ # Check if index is near the boundaries
179
+ if i <= 30 or i >= (len(velocities) - 30):
180
+ # Use linear interpolation near the boundaries
181
+ modified_data[i] = np.around((velocities[i - 1] + modified_data[i:][~np.isnan(modified_data[i:])][0]) / 2, 4)
182
+ else:
183
+ # Use cubic polynomial interpolation
184
+ yint = np.delete(np.append(velocities[i - 13:i], modified_data[i:][~np.isnan(modified_data[i:])][0:12]), 12)
185
+ f = interp1d(x, yint, 3)
186
+ modified_data[i] = f(13)
187
+ return np.around(modified_data, decimals=decimals)
File without changes