paradigma 0.3.1__py3-none-any.whl → 0.4.0__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 (122) hide show
  1. paradigma/assets/gait_detection_clf_package.pkl +0 -0
  2. paradigma/assets/gait_filtering_clf_package.pkl +0 -0
  3. paradigma/assets/ppg_quality_clf_package.pkl +0 -0
  4. paradigma/assets/tremor_detection_clf_package.pkl +0 -0
  5. paradigma/classification.py +115 -0
  6. paradigma/config.py +314 -0
  7. paradigma/constants.py +48 -7
  8. paradigma/feature_extraction.py +811 -547
  9. paradigma/pipelines/__init__.py +0 -0
  10. paradigma/pipelines/gait_pipeline.py +727 -0
  11. paradigma/pipelines/heart_rate_pipeline.py +426 -0
  12. paradigma/pipelines/heart_rate_utils.py +780 -0
  13. paradigma/pipelines/tremor_pipeline.py +299 -0
  14. paradigma/preprocessing.py +363 -0
  15. paradigma/segmenting.py +396 -0
  16. paradigma/testing.py +416 -0
  17. paradigma/util.py +393 -16
  18. {paradigma-0.3.1.dist-info → paradigma-0.4.0.dist-info}/METADATA +58 -14
  19. paradigma-0.4.0.dist-info/RECORD +22 -0
  20. {paradigma-0.3.1.dist-info → paradigma-0.4.0.dist-info}/WHEEL +1 -1
  21. paradigma/gait_analysis.py +0 -415
  22. paradigma/gait_analysis_config.py +0 -266
  23. paradigma/heart_rate_analysis.py +0 -127
  24. paradigma/heart_rate_analysis_config.py +0 -9
  25. paradigma/heart_rate_util.py +0 -173
  26. paradigma/imu_preprocessing.py +0 -232
  27. paradigma/ppg/classifier/LR_PPG_quality.pkl +0 -0
  28. paradigma/ppg/classifier/LR_model.mat +0 -0
  29. paradigma/ppg/feat_extraction/acc_feature.m +0 -20
  30. paradigma/ppg/feat_extraction/peakdet.m +0 -64
  31. paradigma/ppg/feat_extraction/ppg_features.m +0 -53
  32. paradigma/ppg/glob_functions/extract_hr_segments.m +0 -37
  33. paradigma/ppg/glob_functions/extract_overlapping_segments.m +0 -23
  34. paradigma/ppg/glob_functions/jsonlab/AUTHORS.txt +0 -41
  35. paradigma/ppg/glob_functions/jsonlab/ChangeLog.txt +0 -74
  36. paradigma/ppg/glob_functions/jsonlab/LICENSE_BSD.txt +0 -25
  37. paradigma/ppg/glob_functions/jsonlab/LICENSE_GPLv3.txt +0 -699
  38. paradigma/ppg/glob_functions/jsonlab/README.txt +0 -394
  39. paradigma/ppg/glob_functions/jsonlab/examples/.svn/entries +0 -368
  40. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_jsonlab_basic.m.svn-base +0 -180
  41. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/demo_ubjson_basic.m.svn-base +0 -180
  42. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example1.json.svn-base +0 -23
  43. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example2.json.svn-base +0 -22
  44. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example3.json.svn-base +0 -11
  45. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/example4.json.svn-base +0 -34
  46. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_basictest.matlab.svn-base +0 -662
  47. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.m.svn-base +0 -27
  48. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_selftest.matlab.svn-base +0 -144
  49. paradigma/ppg/glob_functions/jsonlab/examples/.svn/text-base/jsonlab_speedtest.m.svn-base +0 -21
  50. paradigma/ppg/glob_functions/jsonlab/examples/demo_jsonlab_basic.m +0 -180
  51. paradigma/ppg/glob_functions/jsonlab/examples/demo_ubjson_basic.m +0 -180
  52. paradigma/ppg/glob_functions/jsonlab/examples/example1.json +0 -23
  53. paradigma/ppg/glob_functions/jsonlab/examples/example2.json +0 -22
  54. paradigma/ppg/glob_functions/jsonlab/examples/example3.json +0 -11
  55. paradigma/ppg/glob_functions/jsonlab/examples/example4.json +0 -34
  56. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_basictest.matlab +0 -662
  57. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.m +0 -27
  58. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_selftest.matlab +0 -144
  59. paradigma/ppg/glob_functions/jsonlab/examples/jsonlab_speedtest.m +0 -21
  60. paradigma/ppg/glob_functions/jsonlab/jsonopt.m +0 -32
  61. paradigma/ppg/glob_functions/jsonlab/loadjson.m +0 -566
  62. paradigma/ppg/glob_functions/jsonlab/loadubjson.m +0 -528
  63. paradigma/ppg/glob_functions/jsonlab/mergestruct.m +0 -33
  64. paradigma/ppg/glob_functions/jsonlab/savejson.m +0 -475
  65. paradigma/ppg/glob_functions/jsonlab/saveubjson.m +0 -504
  66. paradigma/ppg/glob_functions/jsonlab/varargin2struct.m +0 -40
  67. paradigma/ppg/glob_functions/sample_prob_final.m +0 -49
  68. paradigma/ppg/glob_functions/synchronization.m +0 -76
  69. paradigma/ppg/glob_functions/tsdf_scan_meta.m +0 -22
  70. paradigma/ppg/hr_functions/Long_TFD_JOT.m +0 -37
  71. paradigma/ppg/hr_functions/PPG_TFD_HR.m +0 -59
  72. paradigma/ppg/hr_functions/TFD toolbox JOT/.gitignore +0 -4
  73. paradigma/ppg/hr_functions/TFD toolbox JOT/CHANGELOG.md +0 -23
  74. paradigma/ppg/hr_functions/TFD toolbox JOT/LICENCE.md +0 -27
  75. paradigma/ppg/hr_functions/TFD toolbox JOT/README.md +0 -251
  76. paradigma/ppg/hr_functions/TFD toolbox JOT/README.pdf +0 -0
  77. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_kern.m +0 -142
  78. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_Doppler_lag_kern.m +0 -314
  79. paradigma/ppg/hr_functions/TFD toolbox JOT/common/gen_lag_kern.m +0 -123
  80. paradigma/ppg/hr_functions/TFD toolbox JOT/dec_tfd.m +0 -154
  81. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_di_gdtfd.m +0 -194
  82. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_li_gdtfd.m +0 -200
  83. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_nonsep_gdtfd.m +0 -229
  84. paradigma/ppg/hr_functions/TFD toolbox JOT/decimated_TFDs/dec_sep_gdtfd.m +0 -241
  85. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/di_gdtfd.m +0 -157
  86. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/li_gdtfd.m +0 -190
  87. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/nonsep_gdtfd.m +0 -196
  88. paradigma/ppg/hr_functions/TFD toolbox JOT/full_TFDs/sep_gdtfd.m +0 -199
  89. paradigma/ppg/hr_functions/TFD toolbox JOT/full_tfd.m +0 -144
  90. paradigma/ppg/hr_functions/TFD toolbox JOT/load_curdir.m +0 -13
  91. paradigma/ppg/hr_functions/TFD toolbox JOT/pics/decimated_TFDs_examples.png +0 -0
  92. paradigma/ppg/hr_functions/TFD toolbox JOT/pics/full_TFDs_examples.png +0 -0
  93. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/check_dec_params_seq.m +0 -79
  94. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispEE.m +0 -9
  95. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/dispVars.m +0 -26
  96. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/disp_bytes.m +0 -25
  97. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_full.m +0 -40
  98. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/fold_vector_half.m +0 -34
  99. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/gen_LFM.m +0 -29
  100. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_analytic_signal.m +0 -76
  101. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/get_window.m +0 -176
  102. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/isreal_fn.m +0 -11
  103. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/padWin.m +0 -97
  104. paradigma/ppg/hr_functions/TFD toolbox JOT/utils/vtfd.m +0 -149
  105. paradigma/ppg/preprocessing/preprocessing_imu.m +0 -15
  106. paradigma/ppg/preprocessing/preprocessing_ppg.m +0 -13
  107. paradigma/ppg_preprocessing.py +0 -313
  108. paradigma/preprocessing_config.py +0 -69
  109. paradigma/quantification.py +0 -58
  110. paradigma/tremor/TremorFeaturesAndClassification.m +0 -345
  111. paradigma/tremor/feat_extraction/DerivativesExtract.m +0 -22
  112. paradigma/tremor/feat_extraction/ExtractBandSignalsRMS.m +0 -72
  113. paradigma/tremor/feat_extraction/MFCCExtract.m +0 -100
  114. paradigma/tremor/feat_extraction/PSDBandPower.m +0 -52
  115. paradigma/tremor/feat_extraction/PSDEst.m +0 -63
  116. paradigma/tremor/feat_extraction/PSDExtrAxis.m +0 -88
  117. paradigma/tremor/feat_extraction/PSDExtrOpt.m +0 -95
  118. paradigma/tremor/preprocessing/InterpData.m +0 -32
  119. paradigma/tremor/weekly_aggregates/WeeklyAggregates.m +0 -295
  120. paradigma/windowing.py +0 -219
  121. paradigma-0.3.1.dist-info/RECORD +0 -108
  122. {paradigma-0.3.1.dist-info → paradigma-0.4.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,396 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+
4
+ from typing import List
5
+ from paradigma.constants import DataColumns
6
+
7
+ import numpy as np
8
+
9
+ def tabulate_windows(
10
+ df: pd.DataFrame,
11
+ columns: List[str],
12
+ window_length_s: float,
13
+ window_step_length_s: float,
14
+ fs: int,
15
+ ) -> np.ndarray:
16
+ """
17
+ Split the given DataFrame into overlapping windows of specified length and step size.
18
+
19
+ This function extracts windows of data from the specified columns of the DataFrame, based on
20
+ the window length and step size provided in the configuration. The windows are returned in
21
+ a 3D NumPy array, where the first dimension represents the window index, the second dimension
22
+ represents the time steps within the window, and the third dimension represents the columns
23
+ of the data.
24
+
25
+ Parameters
26
+ ----------
27
+ df : pd.DataFrame
28
+ The input DataFrame containing the data to be windowed.
29
+ columns : list of str
30
+ A list of column names from the DataFrame that will be used for windowing.
31
+ window_length_s : float
32
+ The length of each window in seconds.
33
+ window_step_length_s : float
34
+ The step size between consecutive windows in seconds.
35
+ fs : int
36
+ The sampling frequency of the data in Hz.
37
+
38
+ Returns
39
+ -------
40
+ np.ndarray
41
+ A 3D NumPy array of shape (n_windows, window_size, n_columns), where:
42
+ - `n_windows` is the number of windows that can be formed from the data.
43
+ - `window_size` is the length of each window in terms of the number of time steps.
44
+ - `n_columns` is the number of columns in the input DataFrame specified by `columns`.
45
+
46
+ If the length of the data is shorter than the specified window size, an empty array is returned.
47
+
48
+ Notes
49
+ -----
50
+ This function uses `np.lib.stride_tricks.sliding_window_view` to generate sliding windows of data.
51
+ The step size is applied to extract windows at intervals.
52
+ If the data is insufficient for at least one window, an empty array will be returned.
53
+
54
+ Example
55
+ -------
56
+ config = Config(window_length_s=5, window_step_length_s=1, sampling_frequency=100)
57
+ df = pd.DataFrame({'col1': np.random.randn(100), 'col2': np.random.randn(100)})
58
+ columns = ['col1', 'col2']
59
+ windows = tabulate_windows(config, df, columns)
60
+ """
61
+ window_size = int(window_length_s * fs)
62
+ window_step_size = int(window_step_length_s * fs)
63
+ n_columns = len(columns)
64
+
65
+ data = df[columns].values
66
+
67
+ # Check if data length is sufficient
68
+ if len(data) < window_size:
69
+ return np.empty((0, window_size, n_columns)) # Return an empty array if insufficient data
70
+
71
+ windows = np.lib.stride_tricks.sliding_window_view(
72
+ data, window_shape=(window_size, n_columns)
73
+ )[::window_step_size].squeeze()
74
+
75
+ # Ensure 3D shape (n_windows, window_size, n_columns)
76
+ if windows.ndim == 2: # Single window case
77
+ windows = windows[np.newaxis, :, :] # Add a new axis at the start
78
+
79
+ return windows
80
+
81
+
82
+ def tabulate_windows_legacy(config, df, agg_func='first'):
83
+ """
84
+ Efficiently creates a windowed dataframe from the input dataframe using vectorized operations.
85
+
86
+ Args:
87
+ df: The input dataframe, where each row represents a timestamp (0.01 sec).
88
+ window_length_s: The number of seconds per window.
89
+ window_step_length_s: The number of seconds to shift between windows.
90
+ single_value_cols: List of columns where a single value (e.g., mean) is needed.
91
+ list_value_cols: List of columns where all 600 values should be stored in a list.
92
+ agg_func: Aggregation function for single-value columns (e.g., 'mean', 'first').
93
+
94
+ Returns:
95
+ The windowed dataframe.
96
+ """
97
+ # If single_value_cols or list_value_cols is None, default to an empty list
98
+ if config.single_value_cols is None:
99
+ config.single_value_cols = []
100
+ if config.list_value_cols is None:
101
+ config.list_value_cols = []
102
+
103
+ window_length = int(config.window_length_s * config.sampling_frequency)
104
+ window_step_size = int(config.window_step_length_s * config.sampling_frequency)
105
+
106
+ n_rows = len(df)
107
+ if window_length > n_rows:
108
+ raise ValueError(f"Window size ({window_length}) cannot be greater than the number of rows ({n_rows}) in the dataframe.")
109
+
110
+ # Create indices for window start positions
111
+ window_starts = np.arange(0, n_rows - window_length + 1, window_step_size)
112
+
113
+ # Prepare the result for the final DataFrame
114
+ result = []
115
+
116
+ # Handle single value columns with vectorized operations
117
+ agg_func_map = {
118
+ 'mean': np.mean,
119
+ 'first': lambda x: x[0],
120
+ }
121
+
122
+ # Check if agg_func is a callable (custom function) or get the function from the map
123
+ if callable(agg_func):
124
+ agg_func_np = agg_func
125
+ else:
126
+ agg_func_np = agg_func_map.get(agg_func, agg_func_map['mean']) # Default to 'mean' if agg_func is not recognized
127
+
128
+
129
+ for window_nr, start in enumerate(window_starts, 1):
130
+ end = start + window_length
131
+ window = df.iloc[start:end]
132
+
133
+ agg_data = {
134
+ 'window_nr': window_nr,
135
+ 'window_start': window[DataColumns.TIME].iloc[0],
136
+ 'window_end': window[DataColumns.TIME].iloc[-1],
137
+ }
138
+
139
+ # Aggregate single-value columns
140
+ for col in config.single_value_cols:
141
+ if col in window.columns: # Only process columns that exist in the window
142
+ agg_data[col] = agg_func_np(window[col].values)
143
+
144
+ # Collect list-value columns efficiently using numpy slicing
145
+ for col in config.list_value_cols:
146
+ if col in window.columns: # Only process columns that exist in the window
147
+ agg_data[col] = window[col].values.tolist()
148
+
149
+ result.append(agg_data)
150
+
151
+ # Convert result list into a DataFrame
152
+ windowed_df = pd.DataFrame(result)
153
+
154
+ # Ensure the column order is as desired: window_nr, window_start, window_end, pre_or_post, and then the rest
155
+ desired_order = ['window_nr', 'window_start', 'window_end'] + config.single_value_cols + config.list_value_cols
156
+
157
+ return windowed_df[desired_order]
158
+
159
+
160
+ def create_segments(
161
+ time_array: np.ndarray,
162
+ max_segment_gap_s: float,
163
+ ):
164
+ # Calculate the difference between consecutive time values
165
+ time_diff = np.diff(time_array, prepend=0.0)
166
+
167
+ # Create a boolean mask for where the gap exceeds the threshold
168
+ gap_exceeds = time_diff > max_segment_gap_s
169
+
170
+ # Create the segment number based on the cumulative sum of the gap_exceeds mask
171
+ segments = gap_exceeds.cumsum() + 1 # +1 to start enumeration from 1
172
+
173
+ return segments
174
+
175
+
176
+ def discard_segments(
177
+ df: pd.DataFrame,
178
+ segment_nr_colname: str,
179
+ min_segment_length_s: float,
180
+ fs: int,
181
+ format: str='timestamps'
182
+ ) -> pd.DataFrame:
183
+ """
184
+ Remove segments smaller than a specified size and reset segment enumeration.
185
+
186
+ This function filters out segments from the DataFrame that are smaller than a
187
+ given minimum size, based on the configuration. After removing small segments,
188
+ the segment numbers are reset to start from 1.
189
+
190
+ Parameters
191
+ ----------
192
+ config : object
193
+ A configuration object containing:
194
+ - `min_segment_length_s`: The minimum segment length in seconds.
195
+ - `sampling_frequency`: The sampling frequency in Hz.
196
+ df : pd.DataFrame
197
+ The input DataFrame containing a segment column and time series data.
198
+ format : str, optional
199
+ The format of the input data, either 'timestamps' or 'windows'.
200
+
201
+ Returns
202
+ -------
203
+ pd.DataFrame
204
+ A filtered DataFrame where small segments have been removed and segment
205
+ numbers have been reset to start from 1.
206
+
207
+ Example
208
+ -------
209
+ config = Config(min_segment_length_s=2, sampling_frequency=100, segment_nr_colname='segment')
210
+ df = pd.DataFrame({
211
+ 'segment': [1, 1, 2, 2, 2],
212
+ 'time': [0, 1, 2, 3, 4]
213
+ })
214
+ df_filtered = discard_segments(config, df)
215
+ # Result:
216
+ # segment time
217
+ # 0 1 0
218
+ # 1 1 1
219
+ # 2 2 2
220
+ # 3 2 3
221
+ # 4 2 4
222
+ """
223
+ # Minimum segment size in number of samples
224
+ if format == 'timestamps':
225
+ min_samples = min_segment_length_s * fs
226
+ elif format == 'windows':
227
+ min_samples = min_segment_length_s
228
+ else:
229
+ raise ValueError("Invalid format. Must be 'timestamps' or 'windows'.")
230
+
231
+ # Group by segment and filter out small segments in one step
232
+ valid_segment_mask = (
233
+ df.groupby(segment_nr_colname)[segment_nr_colname]
234
+ .transform('size') >= min_samples
235
+ )
236
+
237
+ df = df[valid_segment_mask].copy()
238
+
239
+ # Reset segment numbers in a single step
240
+ unique_segments = pd.factorize(df[segment_nr_colname])[0] + 1
241
+ df[segment_nr_colname] = unique_segments
242
+
243
+ return df
244
+
245
+
246
+ def categorize_segments(df, fs, format='timestamps', window_step_length_s=None):
247
+ """
248
+ Categorize segments based on their duration.
249
+
250
+ This function categorizes segments into four categories based on their duration
251
+ in seconds. The categories are defined as:
252
+ - Category 1: Segments shorter than 5 seconds
253
+ - Category 2: Segments between 5 and 10 seconds
254
+ - Category 3: Segments between 10 and 20 seconds
255
+ - Category 4: Segments longer than 20 seconds
256
+
257
+ The duration of each segment is calculated based on the sampling frequency and
258
+ the number of rows (data points) in the segment.
259
+
260
+ Parameters
261
+ ----------
262
+ df : pd.DataFrame
263
+ The input DataFrame containing the segment column with segment numbers.
264
+ config : object
265
+ A configuration object containing `sampling_frequency`.
266
+ format : str, optional
267
+ The format of the input data, either 'timestamps' or 'windows'.
268
+
269
+ Returns
270
+ -------
271
+ pd.Series
272
+ A Series containing the category for each segment:
273
+ - 'short' for segments < 5 seconds
274
+ - 'moderately_long' for segments between 5 and 10 seconds
275
+ - 'long' for segments between 10 and 20 seconds
276
+ - 'very_long' for segments > 20 seconds
277
+ """
278
+ if format == 'windows' and window_step_length_s is None:
279
+ raise ValueError("Window step length must be provided for 'windows' format.")
280
+
281
+ # Define duration thresholds in seconds
282
+ d_max_duration = {
283
+ 'short': 5,
284
+ 'moderately_long': 10,
285
+ 'long': 20
286
+ }
287
+
288
+ # Convert thresholds to rows if format is 'timestamps'
289
+ if format == 'timestamps':
290
+ d_max_duration = {k: v * fs for k, v in d_max_duration.items()}
291
+
292
+ # Count rows per segment
293
+ segment_sizes = df[DataColumns.SEGMENT_NR].value_counts()
294
+
295
+ # Convert segment sizes to duration in seconds
296
+ if format == 'windows':
297
+ segment_sizes *= window_step_length_s
298
+
299
+ # Group by the segment column and apply the categorization
300
+ def categorize(segment_size):
301
+ if segment_size < d_max_duration['short']:
302
+ return 'short'
303
+ elif segment_size < d_max_duration['moderately_long']:
304
+ return 'moderately_long'
305
+ elif segment_size < d_max_duration['long']:
306
+ return 'long'
307
+ else:
308
+ return 'very_long'
309
+
310
+ # Apply categorization to the DataFrame
311
+ return df[DataColumns.SEGMENT_NR].map(segment_sizes).map(categorize).astype('category')
312
+
313
+ class WindowedDataExtractor:
314
+ """
315
+ A utility class for extracting specific column indices and slices
316
+ from a list of windowed column names.
317
+
318
+ Attributes
319
+ ----------
320
+ column_indices : dict
321
+ A dictionary mapping column names to their indices.
322
+
323
+ Methods
324
+ -------
325
+ get_index(col)
326
+ Returns the index of a specific column.
327
+ get_slice(cols)
328
+ Returns a slice object for a range of consecutive columns.
329
+ """
330
+
331
+ def __init__(self, windowed_cols):
332
+ """
333
+ Initialize the WindowedDataExtractor.
334
+
335
+ Parameters
336
+ ----------
337
+ windowed_cols : list of str
338
+ A list of column names in the windowed data.
339
+
340
+ Raises
341
+ ------
342
+ ValueError
343
+ If the list of `windowed_cols` is empty.
344
+ """
345
+ if not windowed_cols:
346
+ raise ValueError("The list of windowed columns cannot be empty.")
347
+ self.column_indices = {col: idx for idx, col in enumerate(windowed_cols)}
348
+
349
+ def get_index(self, col):
350
+ """
351
+ Get the index of a specific column.
352
+
353
+ Parameters
354
+ ----------
355
+ col : str
356
+ The name of the column to retrieve the index for.
357
+
358
+ Returns
359
+ -------
360
+ int
361
+ The index of the specified column.
362
+
363
+ Raises
364
+ ------
365
+ ValueError
366
+ If the column is not found in the `windowed_cols` list.
367
+ """
368
+ if col not in self.column_indices:
369
+ raise ValueError(f"Column '{col}' not found in windowed_cols.")
370
+ return self.column_indices[col]
371
+
372
+ def get_slice(self, cols):
373
+ """
374
+ Get a slice object for a range of consecutive columns.
375
+
376
+ Parameters
377
+ ----------
378
+ cols : list of str
379
+ A list of consecutive column names to define the slice.
380
+
381
+ Returns
382
+ -------
383
+ slice
384
+ A slice object spanning the indices of the given columns.
385
+
386
+ Raises
387
+ ------
388
+ ValueError
389
+ If one or more columns in `cols` are not found in the `windowed_cols` list.
390
+ """
391
+ if not all(col in self.column_indices for col in cols):
392
+ missing = [col for col in cols if col not in self.column_indices]
393
+ raise ValueError(f"The following columns are missing from windowed_cols: {missing}")
394
+ start_idx = self.column_indices[cols[0]]
395
+ end_idx = self.column_indices[cols[-1]] + 1
396
+ return slice(start_idx, end_idx)