timelined_array 0.0.3__tar.gz → 0.0.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: timelined_array
3
- Version: 0.0.3
3
+ Version: 0.0.4
4
4
  Summary: Manage easily 1 or multidimensionnal samples numpy arrays that are time related. Extends numpy without removing any of it's abilities on such arrays.
5
5
  Author-Email: Timothe Jost <timothe.jost@wanadoo.fr>
6
6
  License: MIT
@@ -10,7 +10,7 @@ dependencies = [
10
10
  requires-python = ">=3.11"
11
11
  readme = "README.md"
12
12
  dynamic = []
13
- version = "0.0.3"
13
+ version = "0.0.4"
14
14
 
15
15
  [project.license]
16
16
  text = "MIT"
@@ -0,0 +1,3 @@
1
+ __version__ = "0.0.4"
2
+
3
+ from .time import TimelinedArray, MaskedTimelinedArray, Seconds, Boundary
@@ -3,7 +3,10 @@
3
3
  import numpy as np
4
4
  from logging import getLogger
5
5
  from typing import Tuple, List, Protocol, Type
6
+ from enum import Enum
7
+ import operator
6
8
 
9
+ # class syntax
7
10
 
8
11
  logger = getLogger("timelined_array")
9
12
 
@@ -34,7 +37,20 @@ class TimeCompatibleProtocol(Protocol):
34
37
 
35
38
 
36
39
  class Timeline(np.ndarray):
40
+ _step = None
41
+ _max_step = None
42
+ max_step_mult = 2
43
+
37
44
  def __new__(cls, input_array, uniform_space=False):
45
+ """Create a new instance of the Timeline class.
46
+
47
+ Args:
48
+ input_array: The input array to create the Timeline object from.
49
+ uniform_space (bool): Flag to indicate if a uniformly spaced timeline is desired.
50
+
51
+ Returns:
52
+ Timeline: A new instance of the Timeline class.
53
+ """
38
54
 
39
55
  if uniform_space:
40
56
  # if we want a uniformly spaced timeline from start to stop of the current timeline.
@@ -47,79 +63,241 @@ class Timeline(np.ndarray):
47
63
  return obj
48
64
 
49
65
  def __array_finalize__(self, obj):
66
+ """Finalize the array when subclassing a numpy array.
67
+
68
+ Args:
69
+ self: The subclassed array.
70
+ obj: The original array object being subclassed.
71
+
72
+ Returns:
73
+ None
74
+ """
75
+
50
76
  pass
51
77
 
52
78
  def __setstate__(self, state):
79
+ """Set the state of the object.
80
+
81
+ Args:
82
+ state: The state to set for the object.
83
+ """
84
+
53
85
  # try:
54
86
  # super().__setstate__(state[0:-2]) # old deserializer
55
87
  # except TypeError:
56
88
  super().__setstate__(state) # new one
57
89
 
58
90
  def __contains__(self, time_value):
91
+ """Check if the time_value is within the range of the TimeRange object."""
92
+
59
93
  return self.min() <= time_value <= self.max()
60
94
 
61
95
  def max(self):
96
+ """Return the maximum value in the iterable."""
97
+
62
98
  return super().max().item()
63
99
 
64
100
  def min(self):
101
+ """Return the minimum value in the tensor."""
102
+
65
103
  return super().min().item()
66
104
 
67
105
  @classmethod
68
106
  def _uniformize(cls, timeline):
107
+ """Uniformize the given timeline data.
108
+
109
+ Args:
110
+ cls: The class instance.
111
+ timeline: The timeline data to be uniformized.
112
+
113
+ Raises:
114
+ NotImplementedError: This function is not yet implemented.
115
+
116
+ Returns:
117
+ None
118
+ """
119
+
69
120
  raise NotImplementedError("Upcoming function")
70
121
  # obj = np.linspace(input_array[0], input_array[1], len(input_array)).view(cls)
71
122
  # TODO : do numpy.interp(np.arange(0, len(a), 1.5), np.arange(0, len(a)), a)
72
123
  # interp to get a fixed number of points ?
73
124
 
74
125
  def uniformize(self):
126
+ """Uniformize the elements of the list using the _uniformize method."""
127
+
75
128
  self[:] = self._uniformize(self)
76
129
 
130
+ @property
131
+ def step(self):
132
+ """Mean time between two timeline points. Must be strictly decreasing or increasing to be calculated"""
133
+ if self._step is None:
134
+ diff = np.diff(self)
135
+ # make sure it is continuously rising or decreasing
136
+ if np.all(diff >= 0) or np.all(diff <= 0):
137
+ self._step = np.mean(diff)
138
+ else:
139
+ raise ValueError(
140
+ "Cannot determine the step value of the timeline. "
141
+ "It must be strictly increasing or strictly decreasing."
142
+ )
143
+ return self._step
144
+
145
+ @property
146
+ def max_step(self):
147
+ """Largest time between two timeline points, multiplied by max_step_mult"""
148
+ if self._max_step is None:
149
+ diff = np.diff(self)
150
+ self._max_step = diff[np.argmax(np.absolute(diff))]
151
+ return self._max_step * self.max_step_mult
152
+
153
+
154
+ class Boundary(Enum):
155
+ inclusive = 0
156
+ exclusive = 1
157
+ inc = 0
158
+ exc = 1
159
+
160
+ @staticmethod
161
+ def get_operation(boundary, setting):
162
+ """Return the appropriate comparison operator based on the boundary and setting.
163
+
164
+ Args:
165
+ boundary (str): The boundary type, either "start" or "stop".
166
+ setting (Boundary): The setting for the boundary, either inclusive or exclusive.
167
+
168
+ Returns:
169
+ function: The comparison operator based on the boundary and setting.
170
+
171
+ Raises:
172
+ ValueError: If the boundary or setting is invalid.
173
+ """
174
+
175
+ if boundary == "start":
176
+ if setting == Boundary.inclusive:
177
+ return operator.ge
178
+ elif setting == Boundary.exclusive:
179
+ return operator.gt
180
+ else:
181
+ raise ValueError
182
+ elif boundary == "stop":
183
+ if setting == Boundary.inclusive:
184
+ return operator.le
185
+ elif setting == Boundary.exclusive:
186
+ return operator.lt
187
+ else:
188
+ raise ValueError
189
+ else:
190
+ raise ValueError
191
+
77
192
 
78
193
  class TimeIndexer:
79
194
  """The time indexer indexes by default from >= to the time start, and strictly < to time stop"""
80
195
 
196
+ start_mode = Boundary.inclusive
197
+ stop_mode = Boundary.exclusive
198
+
199
+ _start_operation = Boundary.get_operation("start", start_mode)
200
+ _stop_operation = Boundary.get_operation("stop", stop_mode)
201
+
81
202
  def __init__(self, array: TimeCompatibleProtocol):
82
203
  self.array = array
83
204
 
84
- def seconds_to_index(self, index):
205
+ def time_to_index(
206
+ self, time: float | int | slice | Tuple[int | float] | List[float | int | slice | Tuple[int | float]]
207
+ ):
208
+ """Converts time to index based on different input types.
209
+
210
+ Args:
211
+ time (float | int | slice | Tuple[int | float] | List[float | int | slice | Tuple[int | float]]):
212
+ The time value or range to be converted to index.
213
+
214
+ Returns:
215
+ int: The index corresponding to the input time value or range.
216
+
217
+ Raises:
218
+ ValueError: If the input time type is not supported.
219
+ """
85
220
  # argument index may be a slice or a scalar. Units of index should be in second. Returns a slice as index
86
221
  # this is sort of a wrapper for get_iindex that does the heavy lifting.
87
222
  # this function just makes sure to pass arguments to it corectly depending
88
223
  # on if the time index is a single value or a slice.
89
- if isinstance(index, slice):
90
- return self.get_iindex(index.start, index.stop, index.step)
224
+
225
+ if isinstance(time, slice):
226
+ return self.get_iindex(time.start, time.stop, time.step)
227
+ elif isinstance(time, list):
228
+ return np.array([self.time_to_index(t) for t in time])
229
+ elif isinstance(time, tuple):
230
+ return self.get_iindex(*[time[i] if len(time) > i else None for i in range(3)])
231
+ elif isinstance(time, (int, float)):
232
+ return self.get_iindex(sec_start=time).start
91
233
  else:
92
- return self.get_iindex(sec_start=index).start
234
+ raise ValueError("Cannot process time to index ")
235
+
236
+ seconds_to_index = time_to_index
93
237
 
94
238
  def _insert_time_index(self, time_index):
239
+ """Inserts a time index into the full index.
240
+
241
+ Args:
242
+ time_index: The index to be inserted into the full index.
243
+
244
+ Returns:
245
+ tuple: The full index with the time index inserted.
246
+ """
95
247
  # put the integer value at the position of time index at the right position
96
248
  # (time_dimension) in the tuple of all sliced dimensions
249
+
97
250
  full_index = [slice(None)] * len(self.array.shape)
98
251
  full_index[self.array.time_dimension] = time_index
99
252
 
100
253
  return tuple(full_index)
101
254
 
102
255
  def __getitem__(self, index) -> "TimelinedArray | MaskedTimelinedArray | np.ndarray":
256
+ """Get item from TimelinedArray, MaskedTimelinedArray, or np.ndarray based on the given index.
257
+
258
+ Args:
259
+ index: int, float, slice, np.integer, np.floating
260
+ The index to retrieve the item from the array.
261
+
262
+ Returns:
263
+ TimelinedArray | MaskedTimelinedArray | np.ndarray
264
+ The item at the specified index.
265
+
266
+ Raises:
267
+ ValueError: If the index is iterable and not a valid type for indexing on the time dimension.
268
+ """
269
+
103
270
  if hasattr(index, "__iter__"):
104
271
  # if not isinstance(index,(int,float,slice,np.integer,np.floating)):
105
272
  raise ValueError(
106
273
  "Isec allow only indexing on time dimension. Index must be either int, float or slice, not iterable"
107
274
  )
108
275
 
109
- iindex_time = self.seconds_to_index(index)
276
+ iindex_time = self.time_to_index(index)
110
277
  full_iindex = self._insert_time_index(iindex_time)
111
278
  # print("new full index : ",iindex_time)
112
279
  logger.debug(f"About to index over time with iindex_time {iindex_time} and full_iindex {full_iindex}")
113
280
  return self.array[full_iindex]
114
281
 
115
- def get_iindex(self, sec_start=None, sec_stop=None, sec_step=None): # every value here is in seconds
282
+ def get_iindex(self, sec_start=None, sec_stop=None, sec_step=None):
283
+ """Get the index range based on the given start, stop, and step values in seconds.
284
+
285
+ Args:
286
+ sec_start (float): The start time in seconds. If None, start index will be 0.
287
+ sec_stop (float): The stop time in seconds. If None, stop index will be the length of the timeline.
288
+ sec_step (float): The step size in seconds. If None, step size will be 1.
289
+
290
+ Returns:
291
+ slice: A slice object representing the index range based on the given start, stop, and step values.
292
+ """
116
293
  # converts a time index (follows a slice syntax, but in time units) to integer units
117
- timeline_max_step = np.absolute(np.diff(self.array.timeline)).max() * 2
294
+
295
+ timeline_max_step = abs(self.array.timeline.max_step)
118
296
 
119
297
  if sec_start is None:
120
298
  start = 0
121
299
  else:
122
- if sec_start >= self.array.timeline[0]:
300
+ if self._start_operation(sec_start, self.array.timeline[0]):
123
301
  start = np.argmax(self.array.timeline >= sec_start)
124
302
  else:
125
303
  start = 0
@@ -137,7 +315,7 @@ class TimeIndexer:
137
315
  # , i removed this posibility
138
316
  # stop = np.argmin(self.array.timeline<self.array.timeline[-1]+sec_stop)
139
317
  else:
140
- if sec_stop < self.array.timeline[-1]:
318
+ if self._stop_operation(sec_stop, self.array.timeline[-1]):
141
319
  stop = np.argmin(self.array.timeline < sec_stop)
142
320
  else:
143
321
  stop = len(self.array.timeline) - 1
@@ -157,6 +335,12 @@ class TimeIndexer:
157
335
  step = 1
158
336
  return slice(start, stop, step)
159
337
 
338
+ def __call__(self, start=None, stop=None):
339
+ if start is not None:
340
+ self._start_operation = Boundary.get_operation("start", start)
341
+ if stop is not None:
342
+ self._stop_operation = Boundary.get_operation("stop", stop)
343
+
160
344
 
161
345
  class TimeMixin:
162
346
 
@@ -164,6 +348,15 @@ class TimeMixin:
164
348
  timeline: Timeline
165
349
 
166
350
  def _time_dimension_in_axis(self, axis: int | Tuple[int] | None) -> bool:
351
+ """Check if the time dimension is present in the specified axis.
352
+
353
+ Args:
354
+ axis (int | Tuple[int] | None): The axis to check for the time dimension.
355
+
356
+ Returns:
357
+ bool: True if the time dimension is present in the axis, False otherwise.
358
+ """
359
+
167
360
  if (
168
361
  axis is None
169
362
  or axis == self.time_dimension
@@ -173,6 +366,18 @@ class TimeMixin:
173
366
  return False
174
367
 
175
368
  def _get_time_dimension_after_axis_removal(self, axis_removed) -> int:
369
+ """Return the time dimension after removing specified axis.
370
+
371
+ Args:
372
+ axis_removed (int or tuple): The axis or axes to be removed.
373
+
374
+ Returns:
375
+ int: The time dimension after removing the specified axis or axes.
376
+
377
+ Raises:
378
+ ValueError: If the time dimension would be discarded after axis removal.
379
+ """
380
+
176
381
  if not isinstance(axis_removed, tuple):
177
382
  axis_removed = (axis_removed,)
178
383
  axis_removed = sorted(axis_removed)
@@ -187,6 +392,14 @@ class TimeMixin:
187
392
  return final_time_dimension
188
393
 
189
394
  def _get_advanced_indexed_times(self, index):
395
+ """Get advanced indexed times based on the provided index.
396
+
397
+ Args:
398
+ index (np.ndarray): The index to be used for advanced indexing.
399
+
400
+ Returns:
401
+ tuple: A tuple containing the filtered index, timeline, and time dimension.
402
+ """
190
403
 
191
404
  index = np.asarray(index)
192
405
 
@@ -229,6 +442,14 @@ class TimeMixin:
229
442
  )
230
443
 
231
444
  def _get_slice_indexed_times(self, index):
445
+ """Get the indexed times based on the provided index.
446
+
447
+ Args:
448
+ index: Index to be used for slicing the time dimension.
449
+
450
+ Returns:
451
+ Tuple containing the modified index, final timeline, and the new time dimension.
452
+ """
232
453
 
233
454
  # this will store the new time_dimension axis in the newly formed array
234
455
  final_time_dimension = self.time_dimension
@@ -282,6 +503,14 @@ class TimeMixin:
282
503
  return index, final_timeline, final_time_dimension
283
504
 
284
505
  def _get_indexed_times(self, index: int | Tuple[int] | slice | Tuple[slice] | List | np.ndarray):
506
+ """Get indexed times based on the provided index.
507
+
508
+ Args:
509
+ index (int | Tuple[int] | slice | Tuple[slice] | List | np.ndarray): The index to retrieve times from.
510
+
511
+ Returns:
512
+ np.ndarray: The indexed times based on the provided index.
513
+ """
285
514
 
286
515
  # if index is an array or a list, we do advanced indexing.
287
516
  if isinstance(index, (np.ndarray, list)):
@@ -291,9 +520,28 @@ class TimeMixin:
291
520
 
292
521
  @staticmethod
293
522
  def _is_single_element(obj):
523
+ """Check if the input object is a single element.
524
+
525
+ Args:
526
+ obj: Input object to be checked.
527
+
528
+ Returns:
529
+ bool: True if the input object is a single element, False otherwise.
530
+ """
531
+
294
532
  return obj.shape == ()
295
533
 
296
534
  def _finish_axis_removing_operation(self, result: TimeCompatibleProtocol, axis: int | Tuple[int] | None):
535
+ """Finish axis removing operation.
536
+
537
+ Args:
538
+ result (TimeCompatibleProtocol): The result of the operation.
539
+ axis (int | Tuple[int] | None): The axis or axes to remove.
540
+
541
+ Returns:
542
+ TimeCompatibleProtocol: The result after finishing the axis removing operation.
543
+ """
544
+
297
545
  if not isinstance(result, np.ndarray):
298
546
  return result
299
547
  if not self._is_single_element(result):
@@ -306,6 +554,9 @@ class TimeMixin:
306
554
  # # REDUCE and SETSTATE are used to instanciate the array from and to a pickled serialized object.
307
555
  # # We only need to store and retrieve time_dimension and timeline on top of the array's data
308
556
  def __reduce__(self):
557
+ """Return a tuple to be used for pickling and unpickling the
558
+ object with additional attributes 'timeline' and 'time_dimension'."""
559
+
309
560
  # Get the parent's __reduce__ tuple
310
561
  pickled_state = super().__reduce__()
311
562
  # Create our own tuple to pass to __setstate__
@@ -316,6 +567,16 @@ class TimeMixin:
316
567
  return (pickled_state[0], pickled_state[1], new_state)
317
568
 
318
569
  def __setstate__(self: TimeCompatibleProtocol, state):
570
+ """Set the state of the object using the provided state tuple.
571
+
572
+ Args:
573
+ self (TimeCompatibleProtocol): The TimeCompatibleProtocol object.
574
+ state: The state tuple containing information to set the object's attributes.
575
+
576
+ Returns:
577
+ None
578
+ """
579
+
319
580
  self.timeline = state[-2] # Set the info attribute
320
581
  self.time_dimension = state[-1]
321
582
 
@@ -323,9 +584,21 @@ class TimeMixin:
323
584
  super().__setstate__(state[0:-2])
324
585
 
325
586
  def __hash__(self):
587
+ """Return the hash value of the object based on the array and timeline attributes.
588
+
589
+ Returns:
590
+ int: Hash value of the object.
591
+ """
592
+
326
593
  return hash((self.__array__(), self.timeline)) # type: ignore
327
594
 
328
595
  def _get_array_cls(self) -> TimeCompatibleProtocol:
596
+ """Return the class of the array that is compatible with time operations.
597
+
598
+ Returns:
599
+ TimeCompatibleProtocol: The class of the array that is compatible with time operations.
600
+ """
601
+
329
602
  valid_types = [TimelinedArray, TimelinedArray]
330
603
  for vtype in valid_types:
331
604
  if isinstance(self, vtype):
@@ -334,6 +607,12 @@ class TimeMixin:
334
607
 
335
608
  @property
336
609
  def array_info(self: TimeCompatibleProtocol):
610
+ """Return information about the array.
611
+
612
+ Returns:
613
+ str: A string containing the type of the array, its shape, time dimension, and timeline shape.
614
+ """
615
+
337
616
  return (
338
617
  f"{type(self).__name__} of shape {self.shape}, time_dimension {self.time_dimension} "
339
618
  f"and timeline shape {self.timeline.shape}"
@@ -341,11 +620,11 @@ class TimeMixin:
341
620
 
342
621
  @property
343
622
  def itime(self: TimeCompatibleProtocol):
623
+ """Return a TimeIndexer object based on the given TimeCompatibleProtocol object."""
624
+
344
625
  return TimeIndexer(self)
345
626
 
346
- @property
347
- def isec(self: TimeCompatibleProtocol):
348
- return self.itime
627
+ isec = itime
349
628
 
350
629
  def align_trace(self, start: float, element_nb: int):
351
630
  """Aligns the timelined array by making it start from a timepoint in time-units (synchronizing)
@@ -363,6 +642,16 @@ class TimeMixin:
363
642
  return self.itime[start:][:element_nb] # type: ignore
364
643
 
365
644
  def swapaxes(self: TimeCompatibleProtocol, axis1: int, axis2: int):
645
+ """Swap the two specified axes of the TimelinedArray.
646
+
647
+ Args:
648
+ axis1 (int): The first axis to be swapped.
649
+ axis2 (int): The second axis to be swapped.
650
+
651
+ Returns:
652
+ TimeCompatibleProtocol: A new TimelinedArray with the specified axes swapped.
653
+ """
654
+
366
655
  # we re-instanciate a TimelinedArray with view instead of the full constructor : faster
367
656
  cls = self._get_array_cls()
368
657
 
@@ -380,6 +669,15 @@ class TimeMixin:
380
669
  return swapped_array
381
670
 
382
671
  def transpose(self: TimeCompatibleProtocol, *axes):
672
+ """Transpose the array along the specified axes.
673
+
674
+ Args:
675
+ *axes: The axes to transpose the array along. If not provided, transposes the array in reverse order.
676
+
677
+ Returns:
678
+ TimeCompatibleProtocol: The transposed array with updated timeline and time dimension.
679
+ """
680
+
383
681
  if not axes:
384
682
  axes = tuple(range(self.ndim))[::-1]
385
683
 
@@ -399,9 +697,25 @@ class TimeMixin:
399
697
 
400
698
  @property
401
699
  def T(self: TimeCompatibleProtocol):
700
+ """Transposes the object using the transpose method."""
701
+
402
702
  return self.transpose()
403
703
 
404
704
  def moveaxis(self: TimeCompatibleProtocol, source: int | Tuple[int], destination: int | Tuple[int]):
705
+ """Move the axis of the array to new positions.
706
+
707
+ Args:
708
+ source (int or Tuple[int]): The source position(s) of the axis to move.
709
+ destination (int or Tuple[int]): The destination position(s) to move the axis to.
710
+
711
+ Returns:
712
+ TimeCompatibleProtocol: A new array with the axis moved to the specified destination.
713
+
714
+ Note:
715
+ This method re-instantiates a TimelinedArray with a view instead of the full
716
+ constructor for faster performance.
717
+ """
718
+
405
719
  if isinstance(source, int):
406
720
  source = (source,)
407
721
  if isinstance(destination, int):
@@ -430,6 +744,16 @@ class TimeMixin:
430
744
  return moved_array
431
745
 
432
746
  def rollaxis(self: TimeCompatibleProtocol, axis: int, start: int = 0):
747
+ """Roll the axis of the TimelinedArray.
748
+
749
+ Args:
750
+ axis (int): The axis to roll.
751
+ start (int, optional): The position where the axis is placed. Defaults to 0.
752
+
753
+ Returns:
754
+ TimeCompatibleProtocol: A TimelinedArray with the rolled axis.
755
+ """
756
+
433
757
  # we re-instanciate a TimelinedArray with view instead of the full constructor : faster
434
758
  cls = self._get_array_cls()
435
759
 
@@ -454,33 +778,120 @@ class TimeMixin:
454
778
  return rolled_array
455
779
 
456
780
  def mean(self: TimeCompatibleProtocol, axis: int | Tuple[int] | None = None, dtype=None, out=None, keepdims=False):
781
+ """Calculates the mean along the specified axis.
782
+
783
+ Args:
784
+ axis (int | Tuple[int] | None): Axis or axes along which to perform the mean operation. Default is None.
785
+ dtype: Data-type to use in the computation.
786
+ out: Output array where the result is stored.
787
+ keepdims (bool): If True, the reduced dimensions are retained in the output array.
788
+
789
+ Returns:
790
+ ndarray: Mean of the input array along the specified axis.
791
+ """
792
+
457
793
  result = super().mean(axis=axis, dtype=dtype, out=out, keepdims=keepdims)
458
794
  return self._finish_axis_removing_operation(result, axis)
459
795
 
460
796
  # Override other reduction methods similarly if needed
461
797
  def sum(self: TimeCompatibleProtocol, axis: int | Tuple[int] | None = None, dtype=None, out=None, keepdims=False):
798
+ """Calculate the sum along the specified axis.
799
+
800
+ Args:
801
+ axis (int | Tuple[int] | None): Axis or axes along which a sum is performed.
802
+ The default is to sum over all the dimensions of the input array.
803
+ dtype: The type of the returned array and of the accumulator in which the elements are summed.
804
+ If dtype is not specified, it defaults to the dtype of a, unless a has an integer dtype
805
+ with a precision less than that of the default platform integer.
806
+ In that case, the default platform integer is used.
807
+ out: Alternative output array in which to place the result. It must have the same shape
808
+ as the expected output, but the type of the output values will be cast if necessary.
809
+ keepdims (bool): If this is set to True, the axes which are reduced are left
810
+ in the result as dimensions with size one.
811
+ With this option, the result will broadcast correctly against the input array.
812
+
813
+ Returns:
814
+ The sum of the input array along the specified axis.
815
+ """
816
+
462
817
  result = super().sum(axis=axis, dtype=dtype, out=out, keepdims=keepdims)
463
818
  return self._finish_axis_removing_operation(result, axis)
464
819
 
465
820
  def std(
466
821
  self: TimeCompatibleProtocol, axis: int | Tuple[int] | None = None, dtype=None, out=None, ddof=0, keepdims=False
467
822
  ):
823
+ """Calculate the standard deviation along the specified axis.
824
+
825
+ Args:
826
+ axis (int or Tuple[int] or None): Axis or axes along which the standard deviation is computed.
827
+ The default is to compute the standard deviation of the flattened array.
828
+ dtype: Data-type of the result. If not provided, the data-type of the input is used.
829
+ out: Output array with the same shape as input array, placed with the result.
830
+ ddof (int): Delta degrees of freedom. The divisor used in calculations is N - ddof,
831
+ where N represents the number of elements along the specified axis.
832
+ keepdims (bool): If this is set to True, the axes which are reduced
833
+ are left in the result as dimensions with size one.
834
+
835
+ Returns:
836
+ ndarray: A new array containing the standard deviation
837
+ of elements along the specified axis after removing the axis.
838
+ """
839
+
468
840
  result = super().std(axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)
469
841
  return self._finish_axis_removing_operation(result, axis)
470
842
 
471
843
  def var(
472
844
  self: TimeCompatibleProtocol, axis: int | Tuple[int] | None = None, dtype=None, out=None, ddof=0, keepdims=False
473
845
  ):
846
+ """Calculate the variance along the specified axis.
847
+
848
+ Args:
849
+ self (TimeCompatibleProtocol): The input data.
850
+ axis (int | Tuple[int] | None): Axis or axes along which the variance is computed.
851
+ The default is to compute the variance of the flattened array.
852
+ dtype: Data-type of the result. If not provided, the data-type of the input is used.
853
+ out: Alternative output array in which to place the result.
854
+ It must have the same shape as the expected output but the type will be cast if necessary.
855
+ ddof (int): Delta degrees of freedom. The divisor used in calculations is N - ddof,
856
+ where N represents the number of elements along the specified axis.
857
+ keepdims (bool): If this is set to True, the axes which are reduced
858
+ are left in the result as dimensions with size one.
859
+
860
+ Returns:
861
+ ndarray: A new array containing the variance of the input array along the specified axis.
862
+ """
863
+
474
864
  result = super().var(axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims)
475
865
  return self._finish_axis_removing_operation(result, axis)
476
866
 
477
867
  def rebase_timeline(self, at=0):
868
+ """Rebases the timeline of the array.
869
+
870
+ Args:
871
+ at (int): The index of the element to set as time zero. Defaults to 0.
872
+
873
+ Returns:
874
+ array: A modified version of the array with the timeline adjusted.
875
+ """
876
+
478
877
  # returns a modified version of the array, with the first element of the array to time zero,
479
878
  # and shift the rest accordingly
480
879
  cls = self._get_array_cls()
481
880
  return cls(self, timeline=self.timeline - self.timeline[at]) # type: ignore
482
881
 
483
882
  def offset_timeline(self, offset):
883
+ """Returns a modified version of the array with time offset.
884
+
885
+ Args:
886
+ offset: A fixed offset value to set time of all elements in the array relative to their current value.
887
+
888
+ Returns:
889
+ An array with time offset applied.
890
+
891
+ Raises:
892
+ None
893
+ """
894
+
484
895
  # returns a modified version of the array, where we set time of all elements
485
896
  # in array at a fix offset relative to their current value.
486
897
  cls = self._get_array_cls()
@@ -488,18 +899,37 @@ class TimeMixin:
488
899
 
489
900
  @property
490
901
  def pack(self):
902
+ """Returns a TimePacker object initialized with the current instance."""
903
+
491
904
  return TimePacker(self)
492
905
 
493
906
  def sec_max(self):
907
+ """Return the second maximum time from the timeline."""
908
+
494
909
  # get maximum time
495
910
  return self.timeline.max()
496
911
 
497
912
  def sec_min(self):
913
+ """Get the minimum time from the timeline."""
914
+
498
915
  # get minimum time
499
916
  return self.timeline.min()
500
917
 
501
918
  @staticmethod
502
919
  def extract_time_from_data(data, timeline=None, time_dimension=None, uniform_space=False):
920
+ """Extracts time-related information from the input data.
921
+
922
+ Args:
923
+ data: The input data from which to extract time-related information.
924
+ timeline: The timeline associated with the data. If not provided, it will be extracted from the input data.
925
+ time_dimension: The dimension representing time in the data. If not provided,
926
+ it will be inferred from the input data.
927
+ uniform_space: A boolean indicating whether the data is uniformly spaced in time.
928
+
929
+ Returns:
930
+ A tuple containing the processed data, timeline, and time dimension.
931
+ """
932
+
503
933
  _unpacking = False
504
934
  # if timeline not explicitely passed as arg, we try to pick up the timeline of the input_array.
505
935
  # will rise after if input_array is not a timelined_array
@@ -555,6 +985,8 @@ class TimePacker:
555
985
  self.array = array
556
986
 
557
987
  def __iter__(self):
988
+ """Returns an iterator object that contains the timeline and the array."""
989
+
558
990
  return iter((self.array.timeline, self.array.__array__()))
559
991
 
560
992
 
@@ -571,7 +1003,7 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
571
1003
  timeline_step: This method returns the average time difference between each consecutive value in the timeline.
572
1004
 
573
1005
  TimelinedArrayIndexer class, which has several methods, including:
574
- seconds_to_index: This method converts time in seconds to index value.
1006
+ time_to_index: This method converts time in seconds to index value.
575
1007
  get_iindex: This method converts time in seconds to a slice object representing time.
576
1008
 
577
1009
  __new__ : This method is used to creates a new instance of the TimelinedArray class. It takes several optional
@@ -590,7 +1022,20 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
590
1022
 
591
1023
  """
592
1024
 
1025
+ TA_Timeline = Timeline
1026
+
593
1027
  def __new__(cls, data, timeline=None, time_dimension: int | None = None, uniform_space=False) -> "TimelinedArray":
1028
+ """Create a new TimelinedArray object from the input data.
1029
+
1030
+ Args:
1031
+ data: The input data to be stored in the TimelinedArray.
1032
+ timeline: The timeline associated with the data (default is None).
1033
+ time_dimension: The dimension representing time in the data (default is None).
1034
+ uniform_space: A boolean flag indicating if the space is uniform (default is False).
1035
+
1036
+ Returns:
1037
+ TimelinedArray: A new TimelinedArray object.
1038
+ """
594
1039
 
595
1040
  data, timeline, time_dimension = TimeMixin.extract_time_from_data(
596
1041
  data, timeline=timeline, time_dimension=time_dimension, uniform_space=uniform_space
@@ -614,6 +1059,15 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
614
1059
  return obj
615
1060
 
616
1061
  def __array_finalize__(self, obj):
1062
+ """Finalize the array with additional attributes.
1063
+
1064
+ Args:
1065
+ obj: Another array to finalize.
1066
+
1067
+ Returns:
1068
+ None
1069
+ """
1070
+
617
1071
  super().__array_finalize__(obj)
618
1072
  if obj is None:
619
1073
  return
@@ -621,6 +1075,20 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
621
1075
  self.time_dimension = getattr(obj, "time_dimension", 0)
622
1076
 
623
1077
  def __array_wrap__(self, out_arr, context=None):
1078
+ """Wrap the output array after a ufunc operation.
1079
+
1080
+ Args:
1081
+ out_arr: The output array to be wrapped.
1082
+ context: Additional context information (default is None).
1083
+
1084
+ Returns:
1085
+ The wrapped output array.
1086
+
1087
+ Example:
1088
+ If context is provided, it logs the ufunc operation name.
1089
+ If the shape of the output array is reduced, it logs the shape changes.
1090
+ """
1091
+
624
1092
  if context is not None:
625
1093
  logger.debug(f"wrapping array after ufunc {context[0].__name__}")
626
1094
  output = super().__array_wrap__(out_arr, context)
@@ -629,12 +1097,33 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
629
1097
  return output
630
1098
 
631
1099
  def __array_function__(self, func, types, args, kwargs):
1100
+ """Intercepts array before calling a function.
1101
+
1102
+ Args:
1103
+ self: The array object.
1104
+ func: The function being called.
1105
+ types: The types of the arguments.
1106
+ args: The arguments passed to the function.
1107
+ kwargs: The keyword arguments passed to the function.
1108
+
1109
+ Returns:
1110
+ The result of calling the function on the array.
1111
+ """
1112
+
632
1113
  logger.debug(f"intercepting array before function {func.__name__}")
633
1114
  return super().__array_function__(func, types, args, kwargs)
634
1115
 
635
1116
  def __getitem__(
636
1117
  self, index: int | Tuple[int] | slice | Tuple[slice] | List | np.ndarray
637
1118
  ) -> "TimelinedArray | np.ndarray":
1119
+ """Get item from TimelinedArray based on index or slice.
1120
+
1121
+ Args:
1122
+ index (int | Tuple[int] | slice | Tuple[slice] | List | np.ndarray): Index or slice to retrieve item.
1123
+
1124
+ Returns:
1125
+ TimelinedArray | np.ndarray: Indexed result based on the provided index.
1126
+ """
638
1127
 
639
1128
  index, final_timeline, final_time_dimension = self._get_indexed_times(index)
640
1129
 
@@ -654,13 +1143,27 @@ class TimelinedArray(TimeMixin, np.ndarray, TimeCompatibleProtocol):
654
1143
  # __repr__ and __str__ ARE OVERRIDEN TO AVOID HORRIBLE PERFORMANCE WHEN PRINTING
655
1144
  # DUE TO CUSTOM __GETITEM__ PRE-CHECKS WITH RECURSIVE NATIVE NUMPY REPR
656
1145
  def __repr__(self):
1146
+ """Return a string representation of the object with the class name and the array representation."""
1147
+
657
1148
  return type(self).__name__ + np.array(self).__repr__()[5:]
658
1149
 
659
1150
  def __str__(self):
1151
+ """Return a string representation of the object by concatenating the class name with the string
1152
+ representation of the object as a NumPy array."""
1153
+
660
1154
  return type(self).__name__ + np.array(self).__str__()
661
1155
 
662
1156
  @staticmethod
663
1157
  def align_from_iterable(iterable) -> "TimelinedArray":
1158
+ """Aligns arrays from an iterable based on their timelines.
1159
+
1160
+ Args:
1161
+ iterable: An iterable containing TimelinedArray objects to align.
1162
+
1163
+ Returns:
1164
+ TimelinedArray: A new TimelinedArray object containing aligned arrays.
1165
+ """
1166
+
664
1167
  start = min([item.timeline.min() for item in iterable])
665
1168
  maxlen = min([len(item.isec[start:]) for item in iterable])
666
1169
 
@@ -687,6 +1190,26 @@ class MaskedTimelinedArray(TimeMixin, np.ma.MaskedArray, TimeCompatibleProtocol)
687
1190
  uniform_space=False,
688
1191
  **kwargs,
689
1192
  ):
1193
+ """Create a new instance of the class with the specified parameters.
1194
+
1195
+ Args:
1196
+ cls: The class.
1197
+ data: The data to be used.
1198
+ mask: The mask for the data (default is np.ma.nomask).
1199
+ dtype: The data type (default is None).
1200
+ copy: Whether to copy the data (default is False).
1201
+ fill_value: The fill value for the data (default is None).
1202
+ keep_mask: Whether to keep the mask (default is True).
1203
+ hard_mask: Whether to use a hard mask (default is False).
1204
+ shrink: Whether to shrink the data (default is True).
1205
+ timeline: The timeline for the data.
1206
+ time_dimension: The time dimension for the data.
1207
+ uniform_space: Whether the space is uniform (default is False).
1208
+ **kwargs: Additional keyword arguments.
1209
+
1210
+ Returns:
1211
+ An instance of the class with the specified parameters.
1212
+ """
690
1213
 
691
1214
  _, timeline, time_dimension = TimeMixin.extract_time_from_data(
692
1215
  data, timeline=timeline, time_dimension=time_dimension, uniform_space=uniform_space
@@ -710,6 +1233,15 @@ class MaskedTimelinedArray(TimeMixin, np.ma.MaskedArray, TimeCompatibleProtocol)
710
1233
  return obj
711
1234
 
712
1235
  def __array_finalize__(self, obj):
1236
+ """Finalize the array with additional attributes.
1237
+
1238
+ Args:
1239
+ obj: Another array to finalize.
1240
+
1241
+ Returns:
1242
+ None
1243
+ """
1244
+
713
1245
  super().__array_finalize__(obj)
714
1246
  if obj is None:
715
1247
  return
@@ -719,6 +1251,14 @@ class MaskedTimelinedArray(TimeMixin, np.ma.MaskedArray, TimeCompatibleProtocol)
719
1251
  def __getitem__(
720
1252
  self, index: int | Tuple[int] | slice | Tuple[slice] | List | np.ndarray
721
1253
  ) -> "MaskedTimelinedArray | np.ma.MaskedArray":
1254
+ """Get item from the MaskedTimelinedArray based on the provided index.
1255
+
1256
+ Args:
1257
+ index (int | Tuple[int] | slice | Tuple[slice] | List | np.ndarray): The index or slice to retrieve.
1258
+
1259
+ Returns:
1260
+ MaskedTimelinedArray | np.ma.MaskedArray: The masked array or MaskedTimelinedArray based on the index.
1261
+ """
722
1262
 
723
1263
  index, final_timeline, final_time_dimension = self._get_indexed_times(index)
724
1264
 
@@ -1,3 +0,0 @@
1
- __version__ = "0.0.3"
2
-
3
- from .time import TimelinedArray, MaskedTimelinedArray, Seconds
File without changes