/bottleneck/src/template/move/move_nanstd.py

https://github.com/fhal/bottleneck
Python | 306 lines | 286 code | 16 blank | 4 comment | 4 complexity | 8b3428dd6b077f7c1caa3c7a27b490e1 MD5 | raw file
  1. "move_nanstd template"
  2. from copy import deepcopy
  3. import bottleneck as bn
  4. __all__ = ["move_nanstd"]
  5. FLOAT_DTYPES = [x for x in bn.dtypes if 'float' in x]
  6. INT_DTYPES = [x for x in bn.dtypes if 'int' in x]
  7. # Float dtypes (no axis=None) -----------------------------------------------
  8. floats = {}
  9. floats['dtypes'] = FLOAT_DTYPES
  10. floats['axisNone'] = False
  11. floats['force_output_dtype'] = False
  12. floats['reuse_non_nan_func'] = False
  13. floats['top'] = """
  14. @cython.boundscheck(False)
  15. @cython.wraparound(False)
  16. def NAME_NDIMd_DTYPE_axisAXIS(np.ndarray[np.DTYPE_t, ndim=NDIM] a,
  17. int window, int ddof):
  18. "Moving std of NDIMd array of dtype=DTYPE along axis=AXIS, ignoring NaNs."
  19. cdef Py_ssize_t count = 0
  20. cdef double asum = 0, a2sum = 0, ai
  21. """
  22. loop = {}
  23. loop[1] = """\
  24. if (window < 1) or (window > nINDEX0):
  25. raise ValueError, MOVE_WINDOW_ERR_MSG % (window, nINDEX0)
  26. for iINDEX0 in range(window - 1):
  27. ai = a[INDEXALL]
  28. if ai == ai:
  29. asum += ai
  30. a2sum += ai * ai
  31. count += 1
  32. y[INDEXALL] = NAN
  33. iINDEX0 = window - 1
  34. ai = a[INDEXALL]
  35. if ai == ai:
  36. asum += ai
  37. a2sum += ai * ai
  38. count += 1
  39. if count > 0:
  40. y[INDEXALL] = sqrt((a2sum - asum * asum / count) / (count - ddof))
  41. else:
  42. y[INDEXALL] = NAN
  43. for iINDEX0 in range(window, nINDEX0):
  44. ai = a[INDEXALL]
  45. if ai == ai:
  46. asum += ai
  47. a2sum += ai * ai
  48. count += 1
  49. ai = a[INDEXREPLACE|iAXIS - window|]
  50. if ai == ai:
  51. asum -= ai
  52. a2sum -= ai * ai
  53. count -= 1
  54. if count > 0:
  55. y[INDEXALL] = sqrt((a2sum - asum * asum / count) / (count - ddof))
  56. else:
  57. y[INDEXALL] = NAN
  58. return y
  59. """
  60. loop[2] = """\
  61. if (window < 1) or (window > nAXIS):
  62. raise ValueError, MOVE_WINDOW_ERR_MSG % (window, nAXIS)
  63. for iINDEX0 in range(nINDEX0):
  64. asum = 0
  65. a2sum = 0
  66. count = 0
  67. for iINDEX1 in range(window - 1):
  68. ai = a[INDEXALL]
  69. if ai == ai:
  70. asum += ai
  71. a2sum += ai * ai
  72. count += 1
  73. y[INDEXALL] = NAN
  74. iINDEX1 = window - 1
  75. ai = a[INDEXALL]
  76. if ai == ai:
  77. asum += ai
  78. a2sum += ai * ai
  79. count += 1
  80. if count > 0:
  81. y[INDEXALL] = sqrt((a2sum - asum * asum / count) / (count - ddof))
  82. else:
  83. y[INDEXALL] = NAN
  84. for iINDEX1 in range(window, nINDEX1):
  85. ai = a[INDEXALL]
  86. if ai == ai:
  87. asum += ai
  88. a2sum += ai * ai
  89. count += 1
  90. ai = a[INDEXREPLACE|iAXIS - window|]
  91. if ai == ai:
  92. asum -= ai
  93. a2sum -= ai * ai
  94. count -= 1
  95. if count > 0:
  96. y[INDEXALL] = sqrt((a2sum - asum * asum / count) \
  97. / (count - ddof))
  98. else:
  99. y[INDEXALL] = NAN
  100. return y
  101. """
  102. loop[3] = """\
  103. if (window < 1) or (window > nAXIS):
  104. raise ValueError, MOVE_WINDOW_ERR_MSG % (window, nAXIS)
  105. for iINDEX0 in range(nINDEX0):
  106. for iINDEX1 in range(nINDEX1):
  107. asum = 0
  108. a2sum = 0
  109. count = 0
  110. for iINDEX2 in range(window - 1):
  111. ai = a[INDEXALL]
  112. if ai == ai:
  113. asum += ai
  114. a2sum += ai * ai
  115. count += 1
  116. y[INDEXALL] = NAN
  117. iINDEX2 = window - 1
  118. ai = a[INDEXALL]
  119. if ai == ai:
  120. asum += ai
  121. a2sum += ai * ai
  122. count += 1
  123. if count > 0:
  124. y[INDEXALL] = sqrt((a2sum - asum * asum / count) \
  125. / (count - ddof))
  126. else:
  127. y[INDEXALL] = NAN
  128. for iINDEX2 in range(window, nINDEX2):
  129. ai = a[INDEXALL]
  130. if ai == ai:
  131. asum += ai
  132. a2sum += ai * ai
  133. count += 1
  134. ai = a[INDEXREPLACE|iAXIS - window|]
  135. if ai == ai:
  136. asum -= ai
  137. a2sum -= ai * ai
  138. count -= 1
  139. if count > 0:
  140. y[INDEXALL] = sqrt((a2sum - asum * asum / count) \
  141. / (count - ddof))
  142. else:
  143. y[INDEXALL] = NAN
  144. return y
  145. """
  146. floats['loop'] = loop
  147. # Int dtypes (no axis=None) ------------------------------------------------
  148. ints = deepcopy(floats)
  149. ints['reuse_non_nan_func'] = True
  150. ints['dtypes'] = INT_DTYPES
  151. # Slow, unaccelerated ndim/dtype --------------------------------------------
  152. slow = {}
  153. slow['name'] = "move_nanstd"
  154. slow['signature'] = "arr, window, ddof"
  155. slow['func'] = "bn.slow.move_nanstd(arr, window, axis=AXIS, ddof=ddof)"
  156. # Template ------------------------------------------------------------------
  157. move_nanstd = {}
  158. move_nanstd['name'] = 'move_nanstd'
  159. move_nanstd['is_reducing_function'] = False
  160. move_nanstd['cdef_output'] = True
  161. move_nanstd['slow'] = slow
  162. move_nanstd['templates'] = {}
  163. move_nanstd['templates']['float'] = floats
  164. move_nanstd['templates']['int'] = ints
  165. move_nanstd['pyx_file'] = 'move/%sbit/move_nanstd.pyx'
  166. move_nanstd['main'] = '''"move_nanstd auto-generated from template"
  167. def move_nanstd(arr, int window, int axis=-1, int ddof=0):
  168. """
  169. Moving window standard deviation along the specified axis, ignoring NaNs.
  170. Unlike bn.nanstd, which uses a more rubust two-pass algorithm, move_nanstd
  171. uses a faster one-pass algorithm.
  172. An example of a one-pass algorithm:
  173. >>> np.sqrt((arr*arr).mean() - arr.mean()**2)
  174. An example of a two-pass algorithm:
  175. >>> np.sqrt(((arr - arr.mean())**2).mean())
  176. Note in the two-pass algorithm the mean must be found (first pass) before
  177. the squared deviation (second pass) can be found.
  178. Parameters
  179. ----------
  180. arr : ndarray
  181. Input array.
  182. window : int
  183. The number of elements in the moving window.
  184. axis : int, optional
  185. The axis over which to perform the moving standard deviation. By
  186. default the moving standard deviation is taken over the last axis
  187. (axis=-1). An axis of None is not allowed.
  188. Returns
  189. -------
  190. y : ndarray
  191. The moving standard deviation of the input array along the specified
  192. axis. The output has the same shape as the input.
  193. Examples
  194. --------
  195. >>> arr = np.array([1.0, 2.0, 3.0, 4.0])
  196. >>> bn.move_nanstd(arr, window=2)
  197. array([ nan, 1.5, 2.5, 3.5])
  198. """
  199. func, arr = move_nanstd_selector(arr, axis)
  200. return func(arr, window, ddof)
  201. def move_nanstd_selector(arr, int axis):
  202. """
  203. Return move_nanstd function and array that matches `arr` and `axis`.
  204. Under the hood Bottleneck uses a separate Cython function for each
  205. combination of ndim, dtype, and axis. A lot of the overhead in
  206. bn.move_nanstd() is in checking that `axis` is within range, converting
  207. `arr` into an array (if it is not already an array), and selecting the
  208. function to use to calculate the moving standard deviation.
  209. You can get rid of the overhead by doing all this before you, for example,
  210. enter an inner loop, by using this function.
  211. Parameters
  212. ----------
  213. arr : array_like
  214. Input array. If `arr` is not an array, a conversion is attempted.
  215. axis : {int, None}
  216. Axis along which the moving standard deviation is to be computed.
  217. Returns
  218. -------
  219. func : function
  220. The moving standard deviation function that matches the number of
  221. dimensions, dtype, and the axis along which you wish to find the
  222. standard deviation.
  223. a : ndarray
  224. If the input array `arr` is not a ndarray, then `a` will contain the
  225. result of converting `arr` into a ndarray otherwise a view is
  226. returned.
  227. Examples
  228. --------
  229. Create a numpy array:
  230. >>> arr = np.array([1.0, 2.0, 3.0, 4.0])
  231. Obtain the function needed to determine the sum of `arr` along axis=0:
  232. >>> window, axis = 2, 0
  233. >>> func, a = bn.move.move_nanstd_selector(arr, axis)
  234. >>> func
  235. <built-in function move_nanstd_1d_float64_axis0>
  236. Use the returned function and array to determine the moving nanstd:
  237. >>> func(a, window)
  238. array([ nan, 1.5, 2.5, 3.5])
  239. """
  240. cdef np.ndarray a
  241. if type(arr) is np.ndarray:
  242. a = arr
  243. else:
  244. a = np.array(arr, copy=False)
  245. cdef int ndim = PyArray_NDIM(a)
  246. cdef int dtype = PyArray_TYPE(a)
  247. if axis < 0:
  248. axis += ndim
  249. cdef tuple key = (ndim, dtype, axis)
  250. try:
  251. func = move_nanstd_dict[key]
  252. except KeyError:
  253. if (axis < 0) or (axis >= ndim):
  254. raise ValueError, "axis(=%d) out of bounds" % axis
  255. try:
  256. func = move_nanstd_slow_dict[axis]
  257. except KeyError:
  258. tup = (str(ndim), str(a.dtype), str(axis))
  259. raise TypeError, "Unsupported ndim/dtype/axis (%s/%s/%s)." % tup
  260. return func, a
  261. '''