Reputation: 969
For a data analysis task, I want to find zero crossings in a numpy array, coming from a convolution with first a sobel-like kernel, and then a mexican hat kernel. Zero crossings allow me to detect edges in the data.
Unfortunately, the data is somewhat noisy and I only want to find zero crossings with a minimal jump size, 20
in the follwing example:
import numpy as np
arr = np.array([12, 15, 9, 8, -1, 1, -12, -10, 10])
Should result in
>>>array([1, 3, 7])
or
>>>array([3, 7])
Where 3
is the index of -1
, just before the middle of the first jump and 7
is the index of -10
I have tried a modification of the following code (source: Efficiently detect sign-changes in python)
zero_crossings = np.where(np.diff(np.sign(np.trunc(arr/10))))[0]
Which correctly ignores small jumps, but puts the zero-crossings at [1,5,7]
What would be an efficient way of doing this?
The definition of minimal jump is not strict, but results should be along the lines of my question.
Edit: For Clarification
arr = np.array([12, 15, 9, 8, -1, 1, -12, -10, 10])
arr_floored = np.trunc(arr/10)
>>>>np.array([10, 10, 0, 0, 0, 0, -10, -10, 10])
sgn = np.sign(arr_floored)
>>>>array([ 1, 1, 0, 0, 0, 0, -1, -1, 1])
dsgn = np.diff(sgn)
>>>>array([ 0, -1, 0, 0, 0, -1, 0, 2])
np.where(dsgn)
>>>>(array([1, 5, 7], dtype=int64),)
Further edgecases:
arr = [10,9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10]
Should result in
>>> np.array([10])
Also just noticed: The problem might be ill-posed(in a mathematical sense). I will clarify it later today.
Upvotes: 9
Views: 1998
Reputation: 3732
Here's a solution that gives the midpoint of crossings involving a noise threshold to filter potentially multiple fluctuations around zero applied across multiple data points. It give the correct answers for the two examples you supplied. However, I've made a couple of assumptions:
ABS(start | end) >= 10
hence I've used the minimum range where this condition holds.
import numpy as np
import pandas as pd
arr = np.array([12, 15, 9, 8, -1, 1, -12, -10, 10])
sgn = pd.Series(np.sign(np.trunc(arr/10)))
trailingEdge = sgn[sgn!=0].diff()
edgeIndex = np.array(trailingEdge[trailingEdge!=0].index)
edgeIndex[:-1] + np.diff(edgeIndex) / 2
gives:
array([3., 7.])
and
arr = [10,9,8,7,6,5,4,3,2,1,0,-1,-2,-3,-4,-5,-6,-7,-8,-9,-10]
gives:
array([10.])
Upvotes: 3
Reputation: 436
I guess you want
import numpy as np
x = np.array([10, -50, -30, 50, 10, 3, -200, -12, 123])
indices = np.where(np.logical_and(np.abs(np.diff(x)) >= 20, np.diff(np.sign(x)) != 0))[0]
read as: indices, where ((absolute differences of x) are larger or equal 20) and (the sign flips)
which returns
array([0, 2, 5, 7])
The usual numpy functions don't cover this case. I would suggest simply adding the first element in the end, via the pad function:
import numpy as np
x = np.array([10, 5, 0, -5, -10])
x = np.pad(x, (0, 1), 'wrap')
indices = np.where(np.logical_and(np.abs(np.diff(x)) >= 20, np.diff(np.sign(x)) != 0))[0]
Upvotes: 3