diff options
Diffstat (limited to 'numpy/lib/function_base.py')
-rw-r--r-- | numpy/lib/function_base.py | 815 |
1 files changed, 815 insertions, 0 deletions
diff --git a/numpy/lib/function_base.py b/numpy/lib/function_base.py new file mode 100644 index 000000000..60e4b4be0 --- /dev/null +++ b/numpy/lib/function_base.py @@ -0,0 +1,815 @@ + +l__all__ = ['logspace', 'linspace', 'round_', + 'select', 'piecewise', 'trim_zeros', + 'copy', 'iterable', 'base_repr', 'binary_repr', + 'diff', 'gradient', 'angle', 'unwrap', 'sort_complex', 'disp', + 'unique', 'extract', 'insert', 'nansum', 'nanmax', 'nanargmax', + 'nanargmin', 'nanmin', 'vectorize', 'asarray_chkfinite', 'average', + 'histogram', 'bincount', 'digitize', 'cov', 'corrcoef', 'msort', + 'median', 'sinc', 'hamming', 'hanning', 'bartlett', 'blackman', + 'kaiser', 'trapz' + ] + +import types +import math +import numeric as _nx +from numeric import ones, zeros, arange, concatenate, array, asarray, empty +from numeric import ScalarType, dot, where, newaxis +from umath import pi, multiply, add, arctan2, maximum, minimum, frompyfunc, \ + isnan, absolute, cos, less_equal, sqrt, sin, mod +from oldnumeric import ravel, nonzero, choose, \ + sometrue, alltrue, reshape, any, all, typecodes, ArrayType, squeeze,\ + sort +from type_check import ScalarType, isscalar +from shape_base import atleast_1d +from twodim_base import diag +from _compiled_base import digitize, bincount, _insert +from ufunclike import sign + +_lkup = {'0':'000', + '1':'001', + '2':'010', + '3':'011', + '4':'100', + '5':'101', + '6':'110', + '7':'111', + 'L':''} + +def binary_repr(num): + """Return the binary representation of the input number as a string. + + This is equivalent to using base_repr with base 2, but about 25x + faster. + """ + ostr = oct(num) + bin = '' + for ch in ostr[1:]: + bin += _lkup[ch] + ind = 0 + while bin[ind] == '0': + ind += 1 + return bin[ind:] + +def base_repr (number, base=2, padding=0): + """Return the representation of a number in any given base. + """ + chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' + + lnb = math.log(base) + res = padding*chars[0] + if number == 0: + return res + chars[0] + exponent = int (math.log (number)/lnb) + while(exponent >= 0): + term = long(base)**exponent + lead_digit = int(number / term) + res += chars[lead_digit] + number -= term*lead_digit + exponent -= 1 + return res +#end Fernando's utilities + + +def linspace(start, stop, num=50, endpoint=True, retstep=False): + """Return evenly spaced numbers. + + Return 'num' evenly spaced samples from 'start' to 'stop'. If + 'endpoint' is True, the last sample is 'stop'. If 'retstep' is + True then return the step value used. + """ + num = int(num) + if num <= 0: + return array([]) + if endpoint: + if num == 1: + return array([start]) + step = (stop-start)/float((num-1)) + else: + step = (stop-start)/float(num) + y = _nx.arange(0, num) * step + start + if retstep: + return y, step + else: + return y + +def logspace(start,stop,num=50,endpoint=True,base=10.0): + """Evenly spaced numbers on a logarithmic scale. + + Computes int(num) evenly spaced exponents from start to stop. + If endpoint=True, then last exponent is stop. + Returns base**exponents. + """ + y = linspace(start,stop,num=num,endpoint=endpoint) + return _nx.power(base,y) + +def iterable(y): + try: iter(y) + except: return 0 + return 1 + +def histogram(a, bins=10, range=None, normed=False): + a = asarray(a).ravel() + if not iterable(bins): + if range is None: + range = (a.min(), a.max()) + mn, mx = [mi+0.0 for mi in range] + if mn == mx: + mn -= 0.5 + mx += 0.5 + bins = linspace(mn, mx, bins, endpoint=False) + + n = sort(a).searchsorted(bins) + n = concatenate([n, [len(a)]]) + n = n[1:]-n[:-1] + + if normed: + db = bins[1] - bins[0] + return 1.0/(a.size*db) * n, bins + else: + return n, bins + +def average(a, axis=0, weights=None, returned=False): + """average(a, axis=0, weights=None, returned=False) + + Average the array over the given axis. If the axis is None, average + over all dimensions of the array. Equivalent to a.mean(axis), but + with a default axis of 0 instead of None. + + If an integer axis is given, this equals: + a.sum(axis) * 1.0 / len(a) + + If axis is None, this equals: + a.sum(axis) * 1.0 / product(a.shape) + + If weights are given, result is: + sum(a * weights) / sum(weights), + where the weights must have a's shape or be 1D with length the + size of a in the given axis. Integer weights are converted to + Float. Not specifying weights is equivalent to specifying + weights that are all 1. + + If 'returned' is True, return a tuple: the result and the sum of + the weights or count of values. The shape of these two results + will be the same. + + Raises ZeroDivisionError if appropriate. (The version in MA does + not -- it returns masked values). + """ + if axis is None: + a = array(a).ravel() + if weights is None: + n = add.reduce(a) + d = len(a) * 1.0 + else: + w = array(weights).ravel() * 1.0 + n = add.reduce(multiply(a, w)) + d = add.reduce(w) + else: + a = array(a) + ash = a.shape + if ash == (): + a.shape = (1,) + if weights is None: + n = add.reduce(a, axis) + d = ash[axis] * 1.0 + if returned: + d = ones(n.shape) * d + else: + w = array(weights, copy=False) * 1.0 + wsh = w.shape + if wsh == (): + wsh = (1,) + if wsh == ash: + n = add.reduce(a*w, axis) + d = add.reduce(w, axis) + elif wsh == (ash[axis],): + ni = ash[axis] + r = [newaxis]*ni + r[axis] = slice(None, None, 1) + w1 = eval("w["+repr(tuple(r))+"]*ones(ash, Float)") + n = add.reduce(a*w1, axis) + d = add.reduce(w1, axis) + else: + raise ValueError, 'averaging weights have wrong shape' + + if not isinstance(d, ArrayType): + if d == 0.0: + raise ZeroDivisionError, 'zero denominator in average()' + if returned: + return n/d, d + else: + return n/d + +def asarray_chkfinite(a): + """Like asarray, but check that no NaNs or Infs are present. + """ + a = asarray(a) + if (a.dtypechar in _nx.typecodes['AllFloat']) \ + and (_nx.isnan(a).any() or _nx.isinf(a).any()): + raise ValueError, "array must not contain infs or NaNs" + return a + + + + +def piecewise(x, condlist, funclist, *args, **kw): + """Return a piecewise-defined function. + + x is the domain + + condlist is a list of boolean arrays or a single boolean array + The length of the condition list must be n2 or n2-1 where n2 + is the length of the function list. If len(condlist)==n2-1, then + an 'otherwise' condition is formed by |'ing all the conditions + and inverting. + + funclist is a list of functions to call of length (n2). + Each function should return an array output for an array input + Each function can take (the same set) of extra arguments and + keyword arguments which are passed in after the function list. + + The output is the same shape and type as x and is found by + calling the functions on the appropriate portions of x. + + Note: This is similar to choose or select, except + the the functions are only evaluated on elements of x + that satisfy the corresponding condition. + + The result is + |-- + | f1(x) for condition1 + y = --| f2(x) for condition2 + | ... + | fn(x) for conditionn + |-- + + """ + n2 = len(funclist) + if not isinstance(condlist, type([])): + condlist = [condlist] + n = len(condlist) + if n == n2-1: # compute the "otherwise" condition. + totlist = condlist[0] + for k in range(1, n): + totlist |= condlist + condlist.append(~totlist) + n += 1 + if (n != n2): + raise ValueError, "function list and condition list must be the same" + y = empty(x.shape, x.dtype) + for k in range(n): + item = funclist[k] + if not callable(item): + y[condlist[k]] = item + else: + y[condlist[k]] = item(x[condlist[k]], *args, **kw) + return y + +def select(condlist, choicelist, default=0): + """ Return an array composed of different elements of choicelist + depending on the list of conditions. + + condlist is a list of condition arrays containing ones or zeros + + choicelist is a list of choice arrays (of the "same" size as the + arrays in condlist). The result array has the "same" size as the + arrays in choicelist. If condlist is [c0, ..., cN-1] then choicelist + must be of length N. The elements of the choicelist can then be + represented as [v0, ..., vN-1]. The default choice if none of the + conditions are met is given as the default argument. + + The conditions are tested in order and the first one statisfied is + used to select the choice. In other words, the elements of the + output array are found from the following tree (notice the order of + the conditions matters): + + if c0: v0 + elif c1: v1 + elif c2: v2 + ... + elif cN-1: vN-1 + else: default + + Note that one of the condition arrays must be large enough to handle + the largest array in the choice list. + """ + n = len(condlist) + n2 = len(choicelist) + if n2 != n: + raise ValueError, "list of cases must be same length as list of conditions" + choicelist.insert(0, default) + S = 0 + pfac = 1 + for k in range(1, n+1): + S += k * pfac * asarray(condlist[k-1]) + if k < n: + pfac *= (1-asarray(condlist[k-1])) + # handle special case of a 1-element condition but + # a multi-element choice + if type(S) in ScalarType or max(asarray(S).shape)==1: + pfac = asarray(1) + for k in range(n2+1): + pfac = pfac + asarray(choicelist[k]) + S = S*ones(asarray(pfac).shape) + return choose(S, tuple(choicelist)) + +def _asarray1d(arr, copy=False): + """Ensure 1D array for one array. + """ + if copy: + return asarray(arr).flatten() + else: + return asarray(arr).ravel() + +def copy(a): + """Return an array copy of the given object. + """ + return array(a, copy=True) + +# Basic operations + +def gradient(f, *varargs): + """Calculate the gradient of an N-dimensional scalar function. + + Uses central differences on the interior and first differences on boundaries + to give the same shape. + + Inputs: + + f -- An N-dimensional array giving samples of a scalar function + + varargs -- 0, 1, or N scalars giving the sample distances in each direction + + Outputs: + + N arrays of the same shape as f giving the derivative of f with respect + to each dimension. + """ + N = len(f.shape) # number of dimensions + n = len(varargs) + if n==0: + dx = [1.0]*N + elif n==1: + dx = [varargs[0]]*N + elif n==N: + dx = list(varargs) + else: + raise SyntaxError, "invalid number of arguments" + + # use central differences on interior and first differences on endpoints + + print dx + outvals = [] + + # create slice objects --- initially all are [:, :, ..., :] + slice1 = [slice(None)]*N + slice2 = [slice(None)]*N + slice3 = [slice(None)]*N + + otype = f.dtypechar + if otype not in ['f', 'd', 'F', 'D']: + otype = 'd' + + for axis in range(N): + # select out appropriate parts for this dimension + out = zeros(f.shape, f.dtypechar) + slice1[axis] = slice(1, -1) + slice2[axis] = slice(2, None) + slice3[axis] = slice(None, -2) + # 1D equivalent -- out[1:-1] = (f[2:] - f[:-2])/2.0 + out[slice1] = (f[slice2] - f[slice3])/2.0 + slice1[axis] = 0 + slice2[axis] = 1 + slice3[axis] = 0 + # 1D equivalent -- out[0] = (f[1] - f[0]) + out[slice1] = (f[slice2] - f[slice3]) + slice1[axis] = -1 + slice2[axis] = -1 + slice3[axis] = -2 + # 1D equivalent -- out[-1] = (f[-1] - f[-2]) + out[slice1] = (f[slice2] - f[slice3]) + + # divide by step size + outvals.append(out / dx[axis]) + + # reset the slice object in this dimension to ":" + slice1[axis] = slice(None) + slice2[axis] = slice(None) + slice3[axis] = slice(None) + + if N == 1: + return outvals[0] + else: + return outvals + + +def diff(a, n=1, axis=-1): + """Calculate the nth order discrete difference along given axis. + """ + if n==0: + return a + if n<0: + raise ValueError, 'order must be non-negative but got ' + `n` + a = asarray(a) + nd = len(a.shape) + slice1 = [slice(None)]*nd + slice2 = [slice(None)]*nd + slice1[axis] = slice(1, None) + slice2[axis] = slice(None, -1) + slice1 = tuple(slice1) + slice2 = tuple(slice2) + if n > 1: + return diff(a[slice1]-a[slice2], n-1, axis=axis) + else: + return a[slice1]-a[slice2] + +def angle(z, deg=0): + """Return the angle of the complex argument z. + """ + if deg: + fact = 180/pi + else: + fact = 1.0 + z = asarray(z) + if (issubclass(z.dtype, _nx.complexfloating)): + zimag = z.imag + zreal = z.real + else: + zimag = 0 + zreal = z + return arctan2(zimag, zreal) * fact + +def unwrap(p, discont=pi, axis=-1): + """Unwrap radian phase p by changing absolute jumps greater than + 'discont' to their 2*pi complement along the given axis. + """ + p = asarray(p) + nd = len(p.shape) + dd = diff(p, axis=axis) + slice1 = [slice(None, None)]*nd # full slices + slice1[axis] = slice(1, None) + ddmod = mod(dd+pi, 2*pi)-pi + _nx.putmask(ddmod, (ddmod==-pi) & (dd > 0), pi) + ph_correct = ddmod - dd; + _nx.putmask(ph_correct, abs(dd)<discont, 0) + up = array(p, copy=True, dtype='d') + up[slice1] = p[slice1] + ph_correct.cumsum(axis) + return up + +def sort_complex(a): + """ Sort 'a' as a complex array using the real part first and then + the imaginary part if the real part is equal (the default sort order + for complex arrays). This function is a wrapper ensuring a complex + return type. + """ + b = array(a,copy=True) + b.sort() + if not issubclass(b.dtype, _nx.complexfloating): + if b.dtypechar in 'bhBH': + return b.astype('F') + elif b.dtypechar == 'g': + return b.astype('G') + else: + return b.astype('D') + else: + return b + +def trim_zeros(filt, trim='fb'): + """ Trim the leading and trailing zeros from a 1D array. + + Example: + >>> import scipy + >>> a = array((0, 0, 0, 1, 2, 3, 2, 1, 0)) + >>> scipy.trim_zeros(a) + array([1, 2, 3, 2, 1]) + """ + first = 0 + trim = trim.upper() + if 'F' in trim: + for i in filt: + if i != 0.: break + else: first = first + 1 + last = len(filt) + if 'B' in trim: + for i in filt[::-1]: + if i != 0.: break + else: last = last - 1 + return filt[first:last] + +def unique(inseq): + """Return unique items from a 1-dimensional sequence. + """ + # Dictionary setting is quite fast. + set = {} + for item in inseq: + set[item] = None + return asarray(set.keys()) + +def extract(condition, arr): + """Return the elements of ravel(arr) where ravel(condition) is True + (in 1D). + + Equivalent to compress(ravel(condition), ravel(arr)). + """ + return _nx.take(ravel(arr), nonzero(ravel(condition))) + +def insert(arr, mask, vals): + """Similar to putmask arr[mask] = vals but the 1D array vals has the + same number of elements as the non-zero values of mask. Inverse of + extract. + """ + return _insert(arr, mask, vals) + +def nansum(a, axis=-1): + """Sum the array over the given axis, treating NaNs as 0. + """ + y = array(a) + if not issubclass(y.dtype, _nx.integer): + y[isnan(a)] = 0 + return y.sum(axis) + +def nanmin(a, axis=-1): + """Find the minimium over the given axis, ignoring NaNs. + """ + y = array(a) + if not issubclass(y.dtype, _nx.integer): + y[isnan(a)] = _nx.inf + return y.min(axis) + +def nanargmin(a, axis=-1): + """Find the indices of the minimium over the given axis ignoring NaNs. + """ + y = array(a) + if not issubclass(y.dtype, _nx.integer): + y[isnan(a)] = _nx.inf + return y.argmin(axis) + +def nanmax(a, axis=-1): + """Find the maximum over the given axis ignoring NaNs. + """ + y = array(a) + if not issubclass(y.dtype, _nx.integer): + y[isnan(a)] = -_nx.inf + return y.max(axis) + +def nanargmax(a, axis=-1): + """Find the maximum over the given axis ignoring NaNs. + """ + y = array(a) + if not issubclass(y.dtype, _nx.integer): + y[isnan(a)] = -_nx.inf + return y.argmax(axis) + +def disp(mesg, device=None, linefeed=True): + """Display a message to the given device (default is sys.stdout) + with or without a linefeed. + """ + if device is None: + import sys + device = sys.stdout + if linefeed: + device.write('%s\n' % mesg) + else: + device.write('%s' % mesg) + device.flush() + return + +class vectorize(object): + """ + vectorize(somefunction, otypes=None, doc=None) + Generalized Function class. + + Description: + + Define a vectorized function which takes nested sequence + objects or scipy arrays as inputs and returns a + scipy array as output, evaluating the function over successive + tuples of the input arrays like the python map function except it uses + the broadcasting rules of scipy. + + Input: + + somefunction -- a Python function or method + + Example: + + def myfunc(a, b): + if a > b: + return a-b + else + return a+b + + vfunc = vectorize(myfunc) + + >>> vfunc([1, 2, 3, 4], 2) + array([3, 4, 1, 2]) + + """ + def __init__(self, pyfunc, otypes='', doc=None): + try: + fcode = pyfunc.func_code + except AttributeError: + raise TypeError, "object is not a callable Python object" + + self.thefunc = pyfunc + self.ufunc = None + self.nin = fcode.co_argcount + if pyfunc.func_defaults: + self.nin_wo_defaults = self.nin - len(pyfunc.func_defaults) + else: + self.nin_wo_defaults = self.nin + self.nout = None + if doc is None: + self.__doc__ = pyfunc.__doc__ + else: + self.__doc__ = doc + if isinstance(otypes, types.StringType): + self.otypes=otypes + else: + raise ValueError, "output types must be a string" + for char in self.otypes: + if char not in typecodes['All']: + raise ValueError, "invalid typecode specified" + self.lastcallargs = 0 + + def __call__(self, *args): + # get number of outputs and output types by calling + # the function on the first entries of args + nargs = len(args) + if (nargs > self.nin) or (nargs < self.nin_wo_defaults): + raise ValueError, "mismatch between python function inputs"\ + " and received arguments" + if self.nout is None or self.otypes == '': + newargs = [] + for arg in args: + newargs.append(asarray(arg).flat[0]) + theout = self.thefunc(*newargs) + if isinstance(theout, types.TupleType): + self.nout = len(theout) + else: + self.nout = 1 + theout = (theout,) + if self.otypes == '': + otypes = [] + for k in range(self.nout): + otypes.append(asarray(theout[k]).dtypechar) + self.otypes = ''.join(otypes) + + if (self.ufunc is None) or (self.lastcallargs != nargs): + self.ufunc = frompyfunc(self.thefunc, nargs, self.nout) + self.lastcallargs = nargs + + if self.nout == 1: + return self.ufunc(*args).astype(self.otypes[0]) + else: + return tuple([x.astype(c) for x, c in zip(self.ufunc(*args), self.otypes)]) + + +def round_(a, decimals=0): + """Round 'a' to the given number of decimal places. Rounding + behaviour is equivalent to Python. + + Return 'a' if the array is not floating point. Round both the real + and imaginary parts separately if the array is complex. + """ + a = asarray(a) + if not issubclass(a.dtype, _nx.inexact): + return a + if issubclass(a.dtype, _nx.complexfloating): + return round_(a.real, decimals) + 1j*round_(a.imag, decimals) + if decimals is not 0: + decimals = asarray(decimals) + s = sign(a) + if decimals is not 0: + a = absolute(multiply(a, 10.**decimals)) + else: + a = absolute(a) + rem = a-asarray(a).astype(_nx.intp) + a = _nx.where(_nx.less(rem, 0.5), _nx.floor(a), _nx.ceil(a)) + # convert back + if decimals is not 0: + return multiply(a, s/(10.**decimals)) + else: + return multiply(a, s) + + +def cov(m,y=None, rowvar=0, bias=0): + """Estimate the covariance matrix. + + If m is a vector, return the variance. For matrices where each row + is an observation, and each column a variable, return the covariance + matrix. Note that in this case diag(cov(m)) is a vector of + variances for each column. + + cov(m) is the same as cov(m, m) + + Normalization is by (N-1) where N is the number of observations + (unbiased estimate). If bias is 1 then normalization is by N. + + If rowvar is zero, then each row is a variable with + observations in the columns. + """ + if y is None: + y = asarray(m) + else: + y = asarray(y) + m = asarray(m) + if rowvar: + m = m.transpose() + y = y.transpose() + if (m.shape[0] == 1): + m = m.transpose() + if (y.shape[0] == 1): + y = y.transpose() + N = m.shape[0] + if (y.shape[0] != N): + raise ValueError, "x and y must have the same number of observations." + m = m - m.mean(axis=0) + y = y - y.mean(axis=0) + if bias: + fact = N*1.0 + else: + fact = N-1.0 + + val = squeeze(dot(m.transpose(),y.conj()) / fact) + return val + +def corrcoef(x, y=None): + """The correlation coefficients + """ + c = cov(x, y) + d = diag(c) + return c/sqrt(multiply.outer(d,d)) + +def blackman(M): + """blackman(M) returns the M-point Blackman window. + """ + n = arange(0,M) + return 0.42-0.5*cos(2.0*pi*n/(M-1)) + 0.08*cos(4.0*pi*n/(M-1)) + +def bartlett(M): + """bartlett(M) returns the M-point Bartlett window. + """ + n = arange(0,M) + return where(less_equal(n,(M-1)/2.0),2.0*n/(M-1),2.0-2.0*n/(M-1)) + +def hanning(M): + """hanning(M) returns the M-point Hanning window. + """ + n = arange(0,M) + return 0.5-0.5*cos(2.0*pi*n/(M-1)) + +def hamming(M): + """hamming(M) returns the M-point Hamming window. + """ + n = arange(0,M) + return 0.54-0.46*cos(2.0*pi*n/(M-1)) + +def kaiser(M,beta): + """kaiser(M, beta) returns a Kaiser window of length M with shape parameter + beta. It depends on scipy.special (in full scipy) for the modified bessel + function i0. + """ + from scipy.special import i0 + n = arange(0,M) + alpha = (M-1)/2.0 + return i0(beta * sqrt(1-((n-alpha)/alpha)**2.0))/i0(beta) + +def sinc(x): + """sinc(x) returns sin(pi*x)/(pi*x) at all points of array x. + """ + y = pi* where(x == 0, 1.0e-20, x) + return sin(y)/y + +def msort(a): + b = array(a,copy=True) + b.sort(0) + return b + +def median(m): + """median(m) returns a median of m along the first dimension of m. + """ + sorted = msort(m) + if sorted.shape[0] % 2 == 1: + return sorted[int(sorted.shape[0]/2)] + else: + sorted = msort(m) + index=sorted.shape[0]/2 + return (sorted[index-1]+sorted[index])/2.0 + +def trapz(y, x=None, dx=1.0, axis=-1): + """Integrate y(x) using samples along the given axis and the composite + trapezoidal rule. If x is None, spacing given by dx is assumed. + """ + y = asarray(y) + if x is None: + d = dx + else: + d = diff(x,axis=axis) + nd = len(y.shape) + slice1 = [slice(None)]*nd + slice2 = [slice(None)]*nd + slice1[axis] = slice(1,None) + slice2[axis] = slice(None,-1) + return add.reduce(d * (y[slice1]+y[slice2])/2.0,axis) |