0

I have a signal where I want to find the average height of the values. This is done by finding the zero crossings and calculating the max and min between each zero crossing, then averaging these values.

My problem occurs when I want to use np.where() to find where the signal is crossing zero. When I use np.where() I get the result in a tuple, but I want it in an array where I can count the amount of times zero is crossed.

I am new to Python and coming from Matlab it is a bit confusing with all the different classes. As you can see, I get an error because nu = len(zero_u) gives 1 as a result, because the whole array is written in a tuple as one element.

Any ideas how to go around this?

The code looks like this:

import numpy as np


def averageheight(f):

    rms = np.std(f)

    f = f + (rms * 10**-6)

    # Find zero crossing

    fsign = np.sign(f)
    fdiff = np.diff(fsign)

    zero_u = np.asarray(np.where(fdiff > 0)) + 1
    zero_d = np.asarray(np.where(fdiff < 0)) + 1

    nu = len(zero_u)
    nd = len(zero_d)
    value_max = np.zeros((nu, 1))
    value_min = np.zeros((nu, 1))
    imaxvec = np.zeros((nu, 1))
    iminvec = np.zeros((nu, 1))

    if (nu > 2) and (nd > 2):

        if zero_u[0] > zero_d[0]:
            zero_d[0] = []

        nu = len(zero_u)
        nd = len(zero_d)
        ncross = np.fmin(nu, nd)

        # Find Maxima:
        for ic in range(0, ncross - 1):
            up = int(zero_u[ic])
            down = int(zero_d[ic])
            fvec = f[up:down]
            value_max[ic] = np.amax(fvec)
            index_max = value_max.argmax()
            imaxvec[ic] = up + index_max - 1

        # Find Minima:
        for ic in range(0, ncross - 2):
            down = int(zero_d[ic])
            up = int(zero_u[ic+1])
            fvec = f[down:up]
            value_min[ic] = np.amin(fvec)
            index_min = value_min.argmin()
            iminvec[ic] = down + index_min - 1

        # Remove spurious values, bumps and zero_d
        thr = rms/3

        maxfind = np.where(value_max < thr)

        for i in range(0, len(maxfind)):
            imaxfind = np.where(value_max == maxfind[i])
            imaxvec[imaxfind] = 0
            value_max[imaxfind] = 0

        minfind = np.where(value_min > -thr)

        for j in range(0, len(minfind)):
            iminfind = np.where(value_min == minfind[j])
            value_min[iminfind] = 0
            iminvec[iminfind] = 0

        # Find Average Height

        avh = np.mean(value_max) - np.mean(value_min)

    else:
        avh = 0

    return avh
MutantOctopus
  • 3,431
  • 4
  • 22
  • 31
Kakemonster
  • 97
  • 2
  • 11
  • When posting code, use backticks (\`, the tilde key) to format small snippets such as your `np.where()`, and for larger blocks, use the `{}` button in the editor. This will add 4 spaces to the start of each line, which formats the code so that it's more easily distinguished. – MutantOctopus Mar 20 '18 at 02:58
  • can you provide an example of the data for calling the averageheight? The provided code runs just fine and doesn't seem to throw any error since you are already using np.asarray() which converts to array. – Ilya Kharlamov Mar 20 '18 at 03:15
  • try `np.asarray(np.where(fdiff>0)[0])+1` – Jason Mar 20 '18 at 03:20
  • FYI: The function `find_transition_times(t, y, threshold)` in [my answer here](https://stackoverflow.com/questions/15112964/digitizing-an-analog-signal/15114952#15114952) might be something you can use. – Warren Weckesser Mar 20 '18 at 03:35
  • I figured out using nu = zero_u.shape[1] gave me the length of the array (the array which is the single element in the tuple). So now I have that figured out. – Kakemonster Mar 20 '18 at 03:36
  • Thanks, Warren Weckesser! That looks like just what I need for my processing – Kakemonster Mar 20 '18 at 03:37

1 Answers1

0

np.where, and np.nonzero even more so, clearly explains that it returns a tuple, with one array for each dimension of the condition array:

In [71]: arr = np.random.randint(-5,5,10)
In [72]: arr
Out[72]: array([ 3,  4,  2, -3, -1,  0, -5,  4,  2, -3])
In [73]: arr.shape
Out[73]: (10,)
In [74]: np.where(arr>=0)
Out[74]: (array([0, 1, 2, 5, 7, 8]),)
In [75]: arr[_]
Out[75]: array([3, 4, 2, 0, 4, 2])

That Out[74] tuple can be used directly as an index.

You can also extract the array from the tuple:

In [76]: np.where(arr>=0)[0]
Out[76]: array([0, 1, 2, 5, 7, 8])

That, I think is a better choice than the np.asarray(np.where(...))

This convention for where becomes clearer when we use it on a 2d array

In [77]: arr2 = arr.reshape(2,5)
In [78]: np.where(arr2>=0)
Out[78]: (array([0, 0, 0, 1, 1, 1]), array([0, 1, 2, 0, 2, 3]))
In [79]: arr2[_]
Out[79]: array([3, 4, 2, 0, 4, 2])

Again we are indexing with a tuple. arr2[1,3] is really arr2[(1,3)]. The values in [] indexing brackets are actually passed to the indexing function as a tuple of values.

np.argwhere applies transpose to the result of where, producing an array:

In [80]: np.transpose(np.where(arr2>=0))
Out[80]: 
array([[0, 0],
       [0, 1],
       [0, 2],
       [1, 0],
       [1, 2],
       [1, 3]])

That's the same indexing arrays, but arranged in a 2d column matrix.

If you need the count of where without the actual values, a slightly faster function is

In [81]: np.count_nonzero(arr>=0)
Out[81]: 6

In fact np.nonzero uses the count to first determine the size of the arrays that it will return.

hpaulj
  • 221,503
  • 14
  • 230
  • 353