[Numpy-discussion] custom accumlators

Jeff Whitaker jswhit at fastmail.fm
Fri Jan 5 16:41:41 EST 2007

Matt Knox wrote:
> I made a post about this a while ago on the scipy-user mailing list, but I didn't receive much of a response so I'm just throwing it out there again (with more detail) in case it got overlooked.
> Basically, I'd like to be able to do accumulate operations with custom functions. numpy.vectorize does not seem to provide an accumulate method with the functions it returns. I'm hoping I don't have to write ufuncs in C to accomplish this, but I fear that may the case. Either way, it would be nice to know if it can, or cannot be done in an easy manner.
> I have lots of examples of where this kind of thing is useful, but I'll just outline two for now.
> Assume the parameter x in all the functions below is a 1-d array
> ---------------------------------------------
> Example 1 - exponential moving average:
> # naive brute force method...
> def expmave(x, k):
>     result = numpy.array(x, copy=True)
>     for i in range(1, result.size):
>        result[i] = result[i-1] + k * (result[i] - result[i-1])
>     return result
> # slicker method (if it worked, which it doesn't)...
> def expmave(x, k):
>     def expmave_sub(a, b):
>         return a + k * (b - a)
>     return numpy.vectorize(expmave_sub).accumulate(x)
> ---------------------------------------------
> Example 2 - forward fill a masked array:
> # naive brute force method...
> def forward_fill(x):
>     result = ma.array(x, copy=True)
>     for i in range(1, result.size):
>        if result[i] is ma.masked: result[i] = result[i-1]
>     return result
> # slicker method (if it worked, which it doesn't)...
> def forward_fill(x):
>     def forward_fill_sub(a, b):
>     	if b is ma.masked: return a
>     	else: return b
>     return numpy.vectorize(forward_fill_sub).accumulate(x)
> ---------------------------------------------
> Is their a good way to do these kinds of things without python looping? Or is that going to require writing a ufunc in C? Any help is greatly appreciated.
> Thanks,
> - Matt Knox

Matt:  Here's a quick and dirty example of how to do this sort of thing 
in pyrex.  I do it all the time, and it works quite well.

# accumulator.pyx:
_doublesize = sizeof(double)
cdef extern from "Python.h":
    int PyObject_AsWriteBuffer(object, void **rbuf, Py_ssize_t *len)
    char *PyString_AsString(object)
def accumulator(object x, double k):
    cdef Py_ssize_t buflen
    cdef int ndim, i
    cdef double *xdata
    cdef void *xbuff
    # make a copy by casting to an array of doubles.
    x = x.astype('f8')
    # if buffer api is supported, get pointer to data buffers.
    if PyObject_AsWriteBuffer(x, &xbuff, &buflen) <> 0:
        raise RuntimeError('object does not support buffer API')
    ndim = buflen/_doublesize
    xdata = <double *>xbuff
    for i from 1 <= i < ndim:
        xdata[i] = xdata[i-1] + k * (xdata[i] - xdata[i-1])
    return x

# test.py
from accumulator import accumulator
from numpy import linspace
x = linspace(1.,10.,10)
k = 0.1
print x
x1 = accumulator(x,k)
print x1
def expmave(x, k):
    result = x.copy()
    for i in range(1, result.size):
        result[i] = result[i-1] + k * (result[i] - result[i-1])
    return result
x2 = expmave(x,k)
print x2 # should be the same as x1

# setup.py
import os
from distutils.core import setup, Extension
from Pyrex.Distutils import build_ext
setup(name = "accumulator",
  cmdclass          = {'build_ext': build_ext},
  keywords          = ["python","map projections","GIS","mapping","maps"],
  ext_modules = [Extension("accumulator",["accumulator.pyx"])])

to build, just do

python setup.py build_ext --inplace

then run test.py.



Jeffrey S. Whitaker         Phone : (303)497-6313
NOAA/OAR/CDC  R/PSD1        FAX   : (303)497-6449
325 Broadway                Boulder, CO, USA 80305-3328

More information about the NumPy-Discussion mailing list