Skip to content

Commit 2cf6804

Browse files
carlosdanielcsantosjreback
carlosdanielcsantos
authored andcommitted
Time-based windows working
‘Closed’ feature is working as expected in time-based windows. Sum tests are passing.
1 parent da034bf commit 2cf6804

File tree

3 files changed

+64
-59
lines changed

3 files changed

+64
-59
lines changed

pandas/core/window.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,8 @@ def count(self):
779779
for b in blocks:
780780
result = b.notnull().astype(int)
781781
result = self._constructor(result, window=window, min_periods=0,
782-
center=self.center).sum()
782+
center=self.center,
783+
closed=self.closed).sum()
783784
results.append(result)
784785

785786
return self._wrap_results(results, blocks, obj)
@@ -801,11 +802,10 @@ def apply(self, func, args=(), kwargs={}):
801802
index, indexi = self._get_index()
802803
closed = self.closed
803804

804-
def f(arg, window, min_periods):
805+
def f(arg, window, min_periods, closed):
805806
minp = _use_window(min_periods, window)
806-
return _window.roll_generic(arg, window, minp, indexi,
807-
offset, func, args,
808-
kwargs, closed)
807+
return _window.roll_generic(arg, window, minp, indexi, closed,
808+
offset, func, args, kwargs)
809809

810810
return self._apply(f, func, args=args, kwargs=kwargs,
811811
center=False)
@@ -876,7 +876,7 @@ def std(self, ddof=1, *args, **kwargs):
876876
def f(arg, *args, **kwargs):
877877
minp = _require_min_periods(1)(self.min_periods, window)
878878
return _zsqrt(_window.roll_var(arg, window, minp, indexi,
879-
ddof))
879+
self.closed, ddof))
880880

881881
return self._apply(f, 'std', check_minp=_require_min_periods(1),
882882
ddof=ddof, **kwargs)
@@ -923,7 +923,7 @@ def quantile(self, quantile, **kwargs):
923923
def f(arg, *args, **kwargs):
924924
minp = _use_window(self.min_periods, window)
925925
return _window.roll_quantile(arg, window, minp, indexi,
926-
quantile)
926+
self.closed, quantile)
927927

928928
return self._apply(f, 'quantile', quantile=quantile,
929929
**kwargs)

pandas/core/window.pyx

+56-51
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ cdef class MockFixedWindowIndexer(WindowIndexer):
161161
162162
"""
163163
def __init__(self, ndarray input, int64_t win, int64_t minp,
164-
object index=None, object floor=None, closed='right'):
164+
object index=None, object floor=None,
165+
bint l_closed=False, bint r_closed=True):
165166

166167
assert index is None
167168
self.is_variable = 0
@@ -194,7 +195,8 @@ cdef class FixedWindowIndexer(WindowIndexer):
194195
195196
"""
196197
def __init__(self, ndarray input, int64_t win, int64_t minp,
197-
object index=None, object floor=None, closed='right'):
198+
object index=None, object floor=None,
199+
bint l_closed=False, bint r_closed=True):
198200
cdef ndarray start_s, start_e, end_s, end_e
199201

200202
assert index is None
@@ -232,7 +234,7 @@ cdef class VariableWindowIndexer(WindowIndexer):
232234
233235
"""
234236
def __init__(self, ndarray input, int64_t win, int64_t minp,
235-
ndarray index, closed='right'):
237+
ndarray index, bint l_closed=False, bint r_closed=True):
236238

237239
self.is_variable = 1
238240
self.N = len(index)
@@ -244,27 +246,14 @@ cdef class VariableWindowIndexer(WindowIndexer):
244246
self.end = np.empty(self.N, dtype='int64')
245247
self.end.fill(-1)
246248

247-
cdef:
248-
bint leftIsClosed = False
249-
bint rightIsClosed = False
250-
251-
if closed not in ['right', 'left', 'both', 'neither']:
252-
closed = 'right'
253-
254-
if closed in ['right', 'both']:
255-
rightIsClosed = True
256-
257-
if closed in ['left', 'both']:
258-
leftIsClosed = True
259-
260-
self.build(index, win, leftIsClosed, rightIsClosed)
249+
self.build(index, win, l_closed, r_closed)
261250

262251
# max window size
263252
self.win = (self.end - self.start).max()
264253

265254

266-
def build(self, ndarray[int64_t] index, int64_t win, bint leftIsClosed,
267-
bint rightIsClosed):
255+
def build(self, ndarray[int64_t] index, int64_t win, bint l_closed,
256+
bint r_closed):
268257

269258
cdef:
270259
ndarray[int64_t] start, end
@@ -276,10 +265,10 @@ cdef class VariableWindowIndexer(WindowIndexer):
276265
N = self.N
277266

278267
start[0] = 0
279-
#if closed in ['right', 'both']:
280-
if rightIsClosed:
268+
269+
if r_closed: # right endpoint is closed
281270
end[0] = 1
282-
else:
271+
else: # right endpoint is open
283272
end[0] = 0
284273

285274
with nogil:
@@ -290,8 +279,7 @@ cdef class VariableWindowIndexer(WindowIndexer):
290279
end_bound = index[i]
291280
start_bound = index[i] - win
292281

293-
#if closed in ['left', 'both']:
294-
if leftIsClosed:
282+
if l_closed: # left endpoint is closed
295283
start_bound -= 1
296284

297285
# advance the start bound until we are
@@ -309,13 +297,13 @@ cdef class VariableWindowIndexer(WindowIndexer):
309297
else:
310298
end[i] = end[i - 1]
311299

312-
#if closed in ['left', 'neither']:
313-
if not rightIsClosed:
300+
# right endpoint is open
301+
if not r_closed:
314302
end[i] -= 1
315303

316304

317-
def get_window_indexer(input, win, minp, index, floor=None,
318-
use_mock=True, closed='right'):
305+
def get_window_indexer(input, win, minp, index, closed,
306+
floor=None, use_mock=True):
319307
"""
320308
return the correct window indexer for the computation
321309
@@ -341,12 +329,28 @@ def get_window_indexer(input, win, minp, index, floor=None,
341329
342330
"""
343331

332+
cdef:
333+
bint l_closed = False
334+
bint r_closed = False
335+
336+
if closed not in ['right', 'left', 'both', 'neither']:
337+
closed = 'right'
338+
339+
if closed in ['right', 'both']:
340+
r_closed = True
341+
342+
if closed in ['left', 'both']:
343+
l_closed = True
344+
344345
if index is not None:
345-
indexer = VariableWindowIndexer(input, win, minp, index, closed)
346+
indexer = VariableWindowIndexer(input, win, minp, index, l_closed,
347+
r_closed)
346348
elif use_mock:
347-
indexer = MockFixedWindowIndexer(input, win, minp, index, floor, closed)
349+
indexer = MockFixedWindowIndexer(input, win, minp, index, floor,
350+
l_closed, r_closed)
348351
else:
349-
indexer = FixedWindowIndexer(input, win, minp, index, floor, closed)
352+
indexer = FixedWindowIndexer(input, win, minp, index, floor, l_closed,
353+
r_closed)
350354
return indexer.get_data()
351355

352356
# ----------------------------------------------------------------------
@@ -436,7 +440,7 @@ cdef inline void remove_sum(double val, int64_t *nobs, double *sum_x) nogil:
436440

437441

438442
def roll_sum(ndarray[double_t] input, int64_t win, int64_t minp,
439-
object index, closed='right'):
443+
object index, str closed):
440444
cdef:
441445
double val, prev_x, sum_x = 0
442446
int64_t s, e
@@ -552,7 +556,7 @@ cdef inline void remove_mean(double val, Py_ssize_t *nobs, double *sum_x,
552556

553557

554558
def roll_mean(ndarray[double_t] input, int64_t win, int64_t minp,
555-
object index, closed='right'):
559+
object index, str closed):
556560
cdef:
557561
double val, prev_x, result, sum_x = 0
558562
int64_t s, e
@@ -677,7 +681,7 @@ cdef inline void remove_var(double val, double *nobs, double *mean_x,
677681

678682

679683
def roll_var(ndarray[double_t] input, int64_t win, int64_t minp,
680-
object index, closed='right', int ddof=1):
684+
object index, str closed, int ddof=1):
681685
"""
682686
Numerically stable implementation using Welford's method.
683687
"""
@@ -820,7 +824,7 @@ cdef inline void remove_skew(double val, int64_t *nobs, double *x, double *xx,
820824

821825

822826
def roll_skew(ndarray[double_t] input, int64_t win, int64_t minp,
823-
object index, closed='right'):
827+
object index, str closed):
824828
cdef:
825829
double val, prev
826830
double x = 0, xx = 0, xxx = 0
@@ -948,7 +952,7 @@ cdef inline void remove_kurt(double val, int64_t *nobs, double *x, double *xx,
948952

949953

950954
def roll_kurt(ndarray[double_t] input, int64_t win, int64_t minp,
951-
object index, closed='right'):
955+
object index, str closed):
952956
cdef:
953957
double val, prev
954958
double x = 0, xx = 0, xxx = 0, xxxx = 0
@@ -1018,7 +1022,7 @@ def roll_kurt(ndarray[double_t] input, int64_t win, int64_t minp,
10181022

10191023

10201024
def roll_median_c(ndarray[float64_t] input, int64_t win, int64_t minp,
1021-
object index, closed='right'):
1025+
object index, str closed):
10221026
cdef:
10231027
double val, res, prev
10241028
bint err=0, is_variable
@@ -1034,8 +1038,8 @@ def roll_median_c(ndarray[float64_t] input, int64_t win, int64_t minp,
10341038
# actual skiplist ops outweigh any window computation costs
10351039
start, end, N, win, minp, is_variable = get_window_indexer(
10361040
input, win,
1037-
minp, index,
1038-
use_mock=False, closed=closed)
1041+
minp, index, closed,
1042+
use_mock=False)
10391043
output = np.empty(N, dtype=float)
10401044

10411045
sl = skiplist_init(<int>win)
@@ -1144,7 +1148,7 @@ cdef inline numeric calc_mm(int64_t minp, Py_ssize_t nobs,
11441148

11451149

11461150
def roll_max(ndarray[numeric] input, int64_t win, int64_t minp,
1147-
object index, closed='right'):
1151+
object index, str closed):
11481152
"""
11491153
Moving max of 1d array of any numeric type along axis=0 ignoring NaNs.
11501154
@@ -1160,11 +1164,11 @@ def roll_max(ndarray[numeric] input, int64_t win, int64_t minp,
11601164
make the interval closed on the right, left,
11611165
both or neither endpoints
11621166
"""
1163-
return _roll_min_max(input, win, minp, index, is_max=1, closed=closed)
1167+
return _roll_min_max(input, win, minp, index, closed=closed, is_max=1)
11641168

11651169

11661170
def roll_min(ndarray[numeric] input, int64_t win, int64_t minp,
1167-
object index, closed='right'):
1171+
object index, str closed):
11681172
"""
11691173
Moving max of 1d array of any numeric type along axis=0 ignoring NaNs.
11701174
@@ -1181,7 +1185,7 @@ def roll_min(ndarray[numeric] input, int64_t win, int64_t minp,
11811185

11821186

11831187
cdef _roll_min_max(ndarray[numeric] input, int64_t win, int64_t minp,
1184-
object index, bint is_max, closed='right'):
1188+
object index, str closed, bint is_max):
11851189
"""
11861190
Moving min/max of 1d array of any numeric type along axis=0
11871191
ignoring NaNs.
@@ -1206,7 +1210,7 @@ cdef _roll_min_max(ndarray[numeric] input, int64_t win, int64_t minp,
12061210

12071211
starti, endi, N, win, minp, is_variable = get_window_indexer(
12081212
input, win,
1209-
minp, index, closed=closed)
1213+
minp, index, closed)
12101214

12111215
output = np.empty(N, dtype=input.dtype)
12121216

@@ -1308,7 +1312,8 @@ cdef _roll_min_max(ndarray[numeric] input, int64_t win, int64_t minp,
13081312

13091313

13101314
def roll_quantile(ndarray[float64_t, cast=True] input, int64_t win,
1311-
int64_t minp, object index, double quantile, closed='right'):
1315+
int64_t minp, object index, str closed,
1316+
double quantile):
13121317
"""
13131318
O(N log(window)) implementation using skip list
13141319
"""
@@ -1328,8 +1333,8 @@ def roll_quantile(ndarray[float64_t, cast=True] input, int64_t win,
13281333
# actual skiplist ops outweigh any window computation costs
13291334
start, end, N, win, minp, is_variable = get_window_indexer(
13301335
input, win,
1331-
minp, index,
1332-
use_mock=False, closed=closed)
1336+
minp, index, closed,
1337+
use_mock=False)
13331338
output = np.empty(N, dtype=float)
13341339
skiplist = IndexableSkiplist(win)
13351340

@@ -1371,9 +1376,9 @@ def roll_quantile(ndarray[float64_t, cast=True] input, int64_t win,
13711376

13721377

13731378
def roll_generic(ndarray[float64_t, cast=True] input,
1374-
int64_t win, int64_t minp, object index,
1379+
int64_t win, int64_t minp, object index, str closed,
13751380
int offset, object func,
1376-
object args, object kwargs, closed='right'):
1381+
object args, object kwargs):
13771382
cdef:
13781383
ndarray[double_t] output, counts, bufarr
13791384
float64_t *buf
@@ -1391,8 +1396,8 @@ def roll_generic(ndarray[float64_t, cast=True] input,
13911396

13921397
start, end, N, win, minp, is_variable = get_window_indexer(input, win,
13931398
minp, index,
1394-
floor=0,
1395-
closed=closed)
1399+
closed,
1400+
floor=0)
13961401
output = np.empty(N, dtype=float)
13971402

13981403
counts = roll_sum(np.concatenate([np.isfinite(input).astype(float),

pandas/tests/test_window.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3410,7 +3410,7 @@ def test_closed(self):
34103410

34113411
expected = df.copy()
34123412
expected["A"] = [np.nan, 1.0, 1, 1, np.nan]
3413-
result = df.rolling('2s', closed='left').sum()
3413+
result = df.rolling('2s', closed='neither').sum()
34143414
tm.assert_frame_equal(result, expected)
34153415

34163416
def test_ragged_sum(self):

0 commit comments

Comments
 (0)