indexers.pyx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. # cython: boundscheck=False, wraparound=False, cdivision=True
  2. import numpy as np
  3. from numpy cimport (
  4. int64_t,
  5. ndarray,
  6. )
  7. # Cython routines for window indexers
  8. def calculate_variable_window_bounds(
  9. int64_t num_values,
  10. int64_t window_size,
  11. object min_periods, # unused but here to match get_window_bounds signature
  12. bint center,
  13. str closed,
  14. const int64_t[:] index
  15. ):
  16. """
  17. Calculate window boundaries for rolling windows from a time offset.
  18. Parameters
  19. ----------
  20. num_values : int64
  21. total number of values
  22. window_size : int64
  23. window size calculated from the offset
  24. min_periods : object
  25. ignored, exists for compatibility
  26. center : bint
  27. center the rolling window on the current observation
  28. closed : str
  29. string of side of the window that should be closed
  30. index : ndarray[int64]
  31. time series index to roll over
  32. Returns
  33. -------
  34. (ndarray[int64], ndarray[int64])
  35. """
  36. cdef:
  37. bint left_closed = False
  38. bint right_closed = False
  39. ndarray[int64_t, ndim=1] start, end
  40. int64_t start_bound, end_bound, index_growth_sign = 1
  41. Py_ssize_t i, j
  42. if num_values <= 0:
  43. return np.empty(0, dtype="int64"), np.empty(0, dtype="int64")
  44. # default is 'right'
  45. if closed is None:
  46. closed = "right"
  47. if closed in ["right", "both"]:
  48. right_closed = True
  49. if closed in ["left", "both"]:
  50. left_closed = True
  51. # GH 43997:
  52. # If the forward and the backward facing windows
  53. # would result in a fraction of 1/2 a nanosecond
  54. # we need to make both interval ends inclusive.
  55. if center and window_size % 2 == 1:
  56. right_closed = True
  57. left_closed = True
  58. if index[num_values - 1] < index[0]:
  59. index_growth_sign = -1
  60. start = np.empty(num_values, dtype="int64")
  61. start.fill(-1)
  62. end = np.empty(num_values, dtype="int64")
  63. end.fill(-1)
  64. start[0] = 0
  65. # right endpoint is closed
  66. if right_closed:
  67. end[0] = 1
  68. # right endpoint is open
  69. else:
  70. end[0] = 0
  71. if center:
  72. end_bound = index[0] + index_growth_sign * window_size / 2
  73. for j in range(0, num_values):
  74. if (index[j] - end_bound) * index_growth_sign < 0:
  75. end[0] = j + 1
  76. elif (index[j] - end_bound) * index_growth_sign == 0 and right_closed:
  77. end[0] = j + 1
  78. elif (index[j] - end_bound) * index_growth_sign >= 0:
  79. end[0] = j
  80. break
  81. with nogil:
  82. # start is start of slice interval (including)
  83. # end is end of slice interval (not including)
  84. for i in range(1, num_values):
  85. if center:
  86. end_bound = index[i] + index_growth_sign * window_size / 2
  87. start_bound = index[i] - index_growth_sign * window_size / 2
  88. else:
  89. end_bound = index[i]
  90. start_bound = index[i] - index_growth_sign * window_size
  91. # left endpoint is closed
  92. if left_closed:
  93. start_bound -= 1 * index_growth_sign
  94. # advance the start bound until we are
  95. # within the constraint
  96. start[i] = i
  97. for j in range(start[i - 1], i):
  98. if (index[j] - start_bound) * index_growth_sign > 0:
  99. start[i] = j
  100. break
  101. # for centered window advance the end bound until we are
  102. # outside the constraint
  103. if center:
  104. for j in range(end[i - 1], num_values + 1):
  105. if j == num_values:
  106. end[i] = j
  107. elif ((index[j] - end_bound) * index_growth_sign == 0 and
  108. right_closed):
  109. end[i] = j + 1
  110. elif (index[j] - end_bound) * index_growth_sign >= 0:
  111. end[i] = j
  112. break
  113. # end bound is previous end
  114. # or current index
  115. elif (index[end[i - 1]] - end_bound) * index_growth_sign <= 0:
  116. end[i] = i + 1
  117. else:
  118. end[i] = end[i - 1]
  119. # right endpoint is open
  120. if not right_closed and not center:
  121. end[i] -= 1
  122. return start, end