/bottleneck/src/template/move/move_std.py

https://github.com/fhal/bottleneck
Python | 396 lines | 374 code | 18 blank | 4 comment | 4 complexity | dda08f8b71b3ef6e94403c0a682e3d5e MD5 | raw file
  1. "move_std template"
  2. from copy import deepcopy
  3. import bottleneck as bn
  4. __all__ = ["move_std"]
  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."
  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 == window:
  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 == window:
  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 == window:
  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 == window:
  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 == window:
  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 == window:
  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['force_output_dtype'] = 'float64'
  150. ints['dtypes'] = INT_DTYPES
  151. ints['top'] += " cdef int winddof\n"
  152. loop = {}
  153. loop[1] = """\
  154. if (window < 1) or (window > nINDEX0):
  155. raise ValueError, MOVE_WINDOW_ERR_MSG % (window, nINDEX0)
  156. winddof = window - ddof
  157. for iINDEX0 in range(window - 1):
  158. ai = a[INDEXALL]
  159. asum += ai
  160. a2sum += ai * ai
  161. y[INDEXALL] = NAN
  162. iINDEX0 = window - 1
  163. ai = a[INDEXALL]
  164. asum += ai
  165. a2sum += ai * ai
  166. y[INDEXALL] = sqrt((a2sum - asum * asum / window) / winddof)
  167. for iINDEX0 in range(window, nINDEX0):
  168. ai = a[INDEXALL]
  169. asum += ai
  170. a2sum += ai * ai
  171. ai = a[INDEXREPLACE|iAXIS - window|]
  172. asum -= ai
  173. a2sum -= ai * ai
  174. y[INDEXALL] = sqrt((a2sum - asum * asum / window) / winddof)
  175. return y
  176. """
  177. loop[2] = """\
  178. if (window < 1) or (window > nAXIS):
  179. raise ValueError, MOVE_WINDOW_ERR_MSG % (window, nAXIS)
  180. winddof = window - ddof
  181. for iINDEX0 in range(nINDEX0):
  182. asum = 0
  183. a2sum = 0
  184. for iINDEX1 in range(window - 1):
  185. ai = a[INDEXALL]
  186. asum += ai
  187. a2sum += ai * ai
  188. y[INDEXALL] = NAN
  189. iINDEX1 = window - 1
  190. ai = a[INDEXALL]
  191. asum += ai
  192. a2sum += ai * ai
  193. y[INDEXALL] = sqrt((a2sum - asum * asum / window) / winddof)
  194. for iINDEX1 in range(window, nINDEX1):
  195. ai = a[INDEXALL]
  196. asum += ai
  197. a2sum += ai * ai
  198. ai = a[INDEXREPLACE|iAXIS - window|]
  199. asum -= ai
  200. a2sum -= ai * ai
  201. y[INDEXALL] = sqrt((a2sum - asum * asum / window) / winddof)
  202. return y
  203. """
  204. loop[3] = """\
  205. if (window < 1) or (window > nAXIS):
  206. raise ValueError, MOVE_WINDOW_ERR_MSG % (window, nAXIS)
  207. winddof = window - ddof
  208. for iINDEX0 in range(nINDEX0):
  209. for iINDEX1 in range(nINDEX1):
  210. asum = 0
  211. a2sum = 0
  212. for iINDEX2 in range(window - 1):
  213. ai = a[INDEXALL]
  214. asum += ai
  215. a2sum += ai * ai
  216. y[INDEXALL] = NAN
  217. iINDEX2 = window - 1
  218. ai = a[INDEXALL]
  219. asum += ai
  220. a2sum += ai * ai
  221. y[INDEXALL] = sqrt((a2sum - asum * asum / window) / winddof)
  222. for iINDEX2 in range(window, nINDEX2):
  223. ai = a[INDEXALL]
  224. asum += ai
  225. a2sum += ai * ai
  226. ai = a[INDEXREPLACE|iAXIS - window|]
  227. asum -= ai
  228. a2sum -= ai * ai
  229. y[INDEXALL] = sqrt((a2sum - asum * asum / window) / winddof)
  230. return y
  231. """
  232. ints['loop'] = loop
  233. # Slow, unaccelerated ndim/dtype --------------------------------------------
  234. slow = {}
  235. slow['name'] = "move_std"
  236. slow['signature'] = "arr, window, ddof"
  237. slow['func'] = "bn.slow.move_std(arr, window, axis=AXIS, ddof=ddof)"
  238. # Template ------------------------------------------------------------------
  239. move_std = {}
  240. move_std['name'] = 'move_std'
  241. move_std['is_reducing_function'] = False
  242. move_std['cdef_output'] = True
  243. move_std['slow'] = slow
  244. move_std['templates'] = {}
  245. move_std['templates']['float'] = floats
  246. move_std['templates']['int'] = ints
  247. move_std['pyx_file'] = 'move/%sbit/move_std.pyx'
  248. move_std['main'] = '''"move_std auto-generated from template"
  249. def move_std(arr, int window, int axis=-1, int ddof=0):
  250. """
  251. Moving window standard deviation along the specified axis.
  252. Unlike bn.nanstd, which uses a more rubust two-pass algorithm, move_std
  253. uses a faster one-pass algorithm.
  254. An example of a one-pass algorithm:
  255. >>> np.sqrt((arr*arr).mean() - arr.mean()**2)
  256. An example of a two-pass algorithm:
  257. >>> np.sqrt(((arr - arr.mean())**2).mean())
  258. Note in the two-pass algorithm the mean must be found (first pass) before
  259. the squared deviation (second pass) can be found.
  260. Parameters
  261. ----------
  262. arr : ndarray
  263. Input array.
  264. window : int
  265. The number of elements in the moving window.
  266. axis : int, optional
  267. The axis over which to perform the moving standard deviation. By
  268. default the moving standard deviation is taken over the last axis
  269. (axis=-1). An axis of None is not allowed.
  270. Returns
  271. -------
  272. y : ndarray
  273. The moving standard deviation of the input array along the specified
  274. axis. The output has the same shape as the input.
  275. Examples
  276. --------
  277. >>> arr = np.array([1.0, 2.0, 3.0, 4.0])
  278. >>> bn.move_std(arr, window=2)
  279. array([ nan, 1.5, 2.5, 3.5])
  280. """
  281. func, arr = move_std_selector(arr, axis)
  282. return func(arr, window, ddof)
  283. def move_std_selector(arr, int axis):
  284. """
  285. Return move_std function and array that matches `arr` and `axis`.
  286. Under the hood Bottleneck uses a separate Cython function for each
  287. combination of ndim, dtype, and axis. A lot of the overhead in
  288. bn.move_std() is in checking that `axis` is within range, converting
  289. `arr` into an array (if it is not already an array), and selecting the
  290. function to use to calculate the moving standard deviation.
  291. You can get rid of the overhead by doing all this before you, for example,
  292. enter an inner loop, by using this function.
  293. Parameters
  294. ----------
  295. arr : array_like
  296. Input array. If `arr` is not an array, a conversion is attempted.
  297. axis : {int, None}
  298. Axis along which the moving standard deviation is to be computed.
  299. Returns
  300. -------
  301. func : function
  302. The moving standard deviation function that matches the number of
  303. dimensions, dtype, and the axis along which you wish to find the
  304. standard deviation.
  305. a : ndarray
  306. If the input array `arr` is not a ndarray, then `a` will contain the
  307. result of converting `arr` into a ndarray otherwise a view is
  308. returned.
  309. Examples
  310. --------
  311. Create a numpy array:
  312. >>> arr = np.array([1.0, 2.0, 3.0, 4.0])
  313. Obtain the function needed to determine the sum of `arr` along axis=0:
  314. >>> window, axis = 2, 0
  315. >>> func, a = bn.move.move_std_selector(arr, axis)
  316. >>> func
  317. <built-in function move_std_1d_float64_axis0>
  318. Use the returned function and array to determine the moving std:
  319. >>> func(a, window)
  320. array([ nan, 1.5, 2.5, 3.5])
  321. """
  322. cdef np.ndarray a
  323. if type(arr) is np.ndarray:
  324. a = arr
  325. else:
  326. a = np.array(arr, copy=False)
  327. cdef int ndim = PyArray_NDIM(a)
  328. cdef int dtype = PyArray_TYPE(a)
  329. if axis < 0:
  330. axis += ndim
  331. cdef tuple key = (ndim, dtype, axis)
  332. try:
  333. func = move_std_dict[key]
  334. except KeyError:
  335. if (axis < 0) or (axis >= ndim):
  336. raise ValueError, "axis(=%d) out of bounds" % axis
  337. try:
  338. func = move_std_slow_dict[axis]
  339. except KeyError:
  340. tup = (str(ndim), str(a.dtype), str(axis))
  341. raise TypeError, "Unsupported ndim/dtype/axis (%s/%s/%s)." % tup
  342. return func, a
  343. '''