Skip to content

Commit 810cf14

Browse files
committed
fix the slice outside the domain for regular ts
1 parent 1f107b7 commit 810cf14

3 files changed

Lines changed: 37 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
88
### Added
99

1010
### Fixed
11+
- Fixed `RegularTimeSeries.slice` returning incorrect domain when slicing entirely outside the data domain. With `reset_origin=False`, the returned empty slice now has its domain clamped to the nearest boundary of the original domain (i.e., `[domain.start, domain.start]` when slicing before the data, or `[domain.end, domain.end]` when slicing after). With `reset_origin=True`, the domain is shifted relative to the slice start, yielding `[0, 0]` in both cases.
1112

1213
### Changed
1314

temporaldata/regular_ts.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,14 +89,11 @@ def select_by_mask(self, mask: np.ndarray):
8989
def _time_to_idx(
9090
self,
9191
time: float,
92-
is_start: bool,
9392
eps: float = 1e-9,
9493
) -> tuple[int, float]:
9594
"""Converts a timestamp to a sample index and its exact reconstructed time.
9695
Args:
9796
time: The timestamp to convert.
98-
is_start: Whether this is the start of a slice (inclusive) or the end
99-
(exclusive). This affects the clamping and time reconstruction.
10097
eps: Tolerance for floating-point precision. If the calculated index
10198
is within ``eps`` of an integer, it is snapped to that integer.
10299
This prevents tiny precision errors (e.g., 3.999999999999999) from
@@ -111,9 +108,10 @@ def _time_to_idx(
111108
domain_end = self.domain.end[0]
112109

113110
# Clamp to domain bounds
114-
if is_start and time <= domain_start:
111+
if time <= domain_start:
115112
return 0, domain_start
116-
if not is_start and time > domain_end:
113+
114+
if time > domain_end:
117115
return len(self), domain_end
118116

119117
# Calculate relative index
@@ -156,17 +154,22 @@ def slice(
156154
containing a subset of the data. The new object will have a modified
157155
:obj:`Interval` domain reflecting the actual sampled boundaries.
158156
"""
159-
start_id, out_start = self._time_to_idx(start, is_start=True, eps=eps)
160-
end_id, out_end = self._time_to_idx(end, is_start=False, eps=eps)
157+
start_id, out_start = self._time_to_idx(start, eps=eps)
158+
end_id, out_end = self._time_to_idx(end, eps=eps)
161159

162160
out = self.__class__.__new__(self.__class__)
163161
out._sampling_rate = self.sampling_rate
164162

165163
out._domain = Interval(start=out_start, end=out_end)
166164

167165
if reset_origin:
168-
out._domain.start = out._domain.start - start
169-
out._domain.end = out._domain.end - start
166+
if out_start == out_end: # slice outside the domain
167+
out._domain.start = out._domain.start - out_start
168+
out._domain.end = out._domain.end - out_end
169+
170+
else:
171+
out._domain.start = out._domain.start - start
172+
out._domain.end = out._domain.end - start
170173

171174
for key in self.keys():
172175
out.__dict__[key] = self.__dict__[key][start_id:end_id].copy()
@@ -339,8 +342,8 @@ def slice(
339342
containing a subset of the data. The new object will have a modified
340343
:obj:`Interval` domain reflecting the actual sampled boundaries.
341344
"""
342-
start_id, out_start = self._time_to_idx(start, is_start=True, eps=eps)
343-
end_id, out_end = self._time_to_idx(end, is_start=False, eps=eps)
345+
start_id, out_start = self._time_to_idx(start, eps=eps)
346+
end_id, out_end = self._time_to_idx(end, eps=eps)
344347

345348
out = self.__class__.__new__(self.__class__)
346349
out._sampling_rate = self.sampling_rate

tests/test_regular_ts.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,3 +316,25 @@ def test_slice_numerical_instability():
316316
assert np.allclose(sliced_ts.timestamps, np.array([0.3, 0.4, 0.5, 0.6, 0.7, 0.8]))
317317
assert sliced_ts.domain.start[0] == 0.3
318318
assert sliced_ts.domain.end[-1] == 0.9
319+
320+
321+
def test_slice_outside_domain():
322+
ts = RegularTimeSeries(
323+
value=np.zeros((100)), sampling_rate=10, domain="auto", domain_start=10.0
324+
)
325+
326+
sliced_ts = ts.slice(0, 5, reset_origin=False)
327+
assert len(sliced_ts) == 0
328+
assert sliced_ts.domain.start[0] == sliced_ts.domain.end[-1] == ts.domain.start[0]
329+
330+
sliced_ts = ts.slice(0, 5, reset_origin=True)
331+
assert len(sliced_ts) == 0
332+
assert sliced_ts.domain.start[0] == sliced_ts.domain.end[-1] == 0.0
333+
334+
sliced_ts = ts.slice(30, 45, reset_origin=False)
335+
assert len(sliced_ts) == 0
336+
assert sliced_ts.domain.start[0] == sliced_ts.domain.end[-1] == ts.domain.end[-1]
337+
338+
sliced_ts = ts.slice(30, 45, reset_origin=True)
339+
assert len(sliced_ts) == 0
340+
assert sliced_ts.domain.start[0] == sliced_ts.domain.end[-1] == 0.0

0 commit comments

Comments
 (0)