@@ -9,7 +9,8 @@ from numpy cimport ndarray, int64_t
9
9
def calculate_variable_window_bounds (
10
10
int64_t num_values ,
11
11
int64_t window_size ,
12
- object min_periods , # unused but here to match get_window_bounds signature
12
+ object step_size_obj ,
13
+ object min_periods_obj ,
13
14
object center , # unused but here to match get_window_bounds signature
14
15
object closed ,
15
16
const int64_t[:] index
@@ -25,8 +26,11 @@ def calculate_variable_window_bounds(
25
26
window_size : int64
26
27
window size calculated from the offset
27
28
29
+ step_size : Optional[int], default None
30
+ the window step size
31
+
28
32
min_periods : object
29
- ignored, exists for compatibility
33
+ Minimum data points in each window.
30
34
31
35
center : object
32
36
ignored, exists for compatibility
@@ -42,68 +46,93 @@ def calculate_variable_window_bounds(
42
46
(ndarray[int64], ndarray[int64])
43
47
"""
44
48
cdef:
45
- bint left_closed = False
46
- bint right_closed = False
47
- int index_growth_sign = 1
49
+ bint left_open = False
50
+ bint right_open = False
51
+ int idx_scalar = 1
48
52
ndarray[int64_t, ndim= 1 ] start, end
49
- int64_t start_bound, end_bound
53
+ int64_t step_size, min_periods
54
+ int64_t index_i, index_si, index_ei,
55
+ int64_t index_window_i, index_step_i
56
+ int64_t index_window_max, index_step_max
57
+ int64_t window_i = 0
58
+ int64_t next_index_si = 0
59
+ int64_t next_index_ei = 0
50
60
Py_ssize_t i, j
51
61
52
- # if windows is variable, default is 'right', otherwise default is 'both'
53
62
if closed is None :
54
- closed = ' right ' if index is not None else ' both '
63
+ closed = ' left '
55
64
56
- if closed in [' right' , ' both' ]:
57
- right_closed = True
65
+ if closed not in [' right' , ' both' ]:
66
+ right_open = True
58
67
59
- if closed in [' left' , ' both' ]:
60
- left_closed = True
68
+ if closed not in [' left' , ' both' ]:
69
+ left_open = True
61
70
71
+ # Assume index is monotonic increasing or decreasing. If decreasing (WHY??) negate values.
62
72
if index[num_values - 1 ] < index[0 ]:
63
- index_growth_sign = - 1
73
+ idx_scalar = - 1
74
+
75
+ # Minimum "observations".
76
+ min_periods = min_periods_obj if min_periods_obj is not None else 0
77
+ step_size = step_size_obj if step_size_obj is not None else 1
64
78
65
79
start = np.empty(num_values, dtype = ' int64' )
66
80
start.fill(- 1 )
67
81
end = np.empty(num_values, dtype = ' int64' )
68
- end.fill(- 1 )
69
82
70
- start[0 ] = 0
83
+ if num_values < 1 :
84
+ return start, end
85
+
86
+ # Indexing into indices: index_si index_ei (index start/end)
87
+ # Indexing into start/end arrays: window_i
88
+ # This will find closed intervals [start, end]
71
89
72
- # right endpoint is closed
73
- if right_closed:
74
- end[0 ] = 1
75
- # right endpoint is open
76
- else :
77
- end[0 ] = 0
90
+ window_i = 0
91
+ next_index_si = 0
92
+ next_index_ei = 0
78
93
79
94
with nogil:
95
+ while next_index_ei < num_values:
96
+ index_si = next_index_si
80
97
81
- # start is start of slice interval (including)
82
- # end is end of slice interval (not including)
83
- for i in range (1 , num_values):
84
- end_bound = index[i]
85
- start_bound = index[i] - index_growth_sign * window_size
86
-
87
- # left endpoint is closed
88
- if left_closed:
89
- start_bound -= 1
90
-
91
- # advance the start bound until we are
92
- # within the constraint
93
- start[i] = i
94
- for j in range (start[i - 1 ], i):
95
- if (index[j] - start_bound) * index_growth_sign > 0 :
96
- start[i] = j
98
+ start[window_i] = index_si
99
+
100
+ index_window_max = index[index_si] + idx_scalar* (window_size - 1 )
101
+ index_step_max = index[index_si] + idx_scalar* (step_size - 1 )
102
+
103
+ # Find end of step.
104
+ index_step_i = num_values - 1
105
+ for index_i in range (index_si + 1 , num_values):
106
+ # Outside of step?
107
+ if idx_scalar* index[index_i] > idx_scalar* index_step_max:
108
+ index_step_i = index_i - 1
97
109
break
98
110
99
- # end bound is previous end
100
- # or current index
101
- if (index[end[i - 1 ]] - end_bound) * index_growth_sign <= 0 :
102
- end[i] = i + 1
103
- else :
104
- end[i] = end[i - 1 ]
105
-
106
- # right endpoint is open
107
- if not right_closed:
108
- end[i] -= 1
109
- return start, end
111
+ # Find end of window.
112
+ index_window_i = num_values - 1
113
+ for index_i in range (next_index_ei + 1 , num_values):
114
+ # Outside of window?
115
+ if idx_scalar* index[index_i] > idx_scalar* index_window_max:
116
+ index_window_i = index_i - 1
117
+ break
118
+
119
+ next_index_si = index_step_i + 1
120
+ next_index_ei = next_index_si if next_index_si > index_window_i + 1 else index_window_i + 1
121
+
122
+ end[window_i] = index_window_i
123
+ window_i += 1
124
+
125
+ # Remove excess slots.
126
+ valid_idx = (start >= 0 ) & (start <= end)
127
+
128
+ # And windows without enough data.
129
+ if min_periods is not None :
130
+ valid_idx &= (end - start + 1 ) >= min_periods
131
+
132
+ # Update open boundaries.
133
+ if left_open:
134
+ start -= 1
135
+ if right_open:
136
+ end += 1
137
+
138
+ return start[valid_idx], end[valid_idx]
0 commit comments