【问题标题】:Find plateau in Numpy array在 Numpy 数组中查找高原
【发布时间】:2018-11-27 03:55:01
【问题描述】:

我正在寻找一种有效的方法来检测其他非常嘈杂的数据中的高原。高原总是相对较宽 一个简单的例子来说明这些数据的样子:

test=np.random.uniform(0.9,1,100)
test[10:20]=0
plt.plot(test)

请注意,可以有多个平台(应该全部检测到),可以有不同的值。

我尝试过使用 scipy.signal.argrelextrema,但它似乎并没有达到我想要的效果:

peaks=argrelextrema(test,np.less,order=25)
plt.vlines(peaks,ymin=0, ymax=1)

我不需要确切的高原区间 - 粗略的范围估计就足够了,只要该估计值大于或等于实际的高原范围即可。但是,它应该是相对有效的。

【问题讨论】:

  • 高原不止一个吗?高原是否总是归零值?如果它们是零值,您需要的不仅仅是获取零值的索引,例如通过np.where(test==0)
  • 嘿,谢谢你的好问题。是的,可以有不止一个高原,不,高原可以有不同的值。将其添加到问题中。
  • 是否存在像this question 那样的中间值(例如 y=0.5),可以可靠地将低值组与高值组区分开来?

标签: python numpy signals


【解决方案1】:

这实际上只是一个“愚蠢”的机器学习任务。您需要编写一个自定义函数来筛选它们。高原有两个关键特征:

  1. 它们是相同值的连续出现(或非常接近)。
  2. 第一个点和最后一个点分别严重偏离前向和后向移动平均线。 (如果您期望加性噪声,请尝试根据标准偏差对其进行量化,对于几何噪声,您还必须考虑信号的幅度。)

然后,一个简单的循环应该足以计算前向移动平均线、该前向移动平均线中的点的标准差、反向移动平均线以及该反向移动平均线中的点的标准差。

  • 阅读,直到找到一个远离常规噪声的点(与方差比较)。开始将这些索引缓冲到列表中。
  • 在索引值相同(或几乎相同,如果您的平台可能有点粗糙)时,继续读取和缓冲该列表中的索引;您需要使用一些容差加上平台的标准偏差,或者只是如果您希望它们都表现相似,则需要一些容忍度)。
  • 如果缓冲区中点的方差变得太高,这不是高原,太粗糙了;把它扔掉,然后从当前位置重新开始扫描。
  • 如果最后一个值与前一个值非常不同(按照触发代码开始缓冲索引的更改顺序)并且与原始脉冲相反,请在此处限制缓冲区;你有一个高原。
  • 现在对这些索引处的点做任何你想做的事。删除它们,用两个边界点之间的线性插值替换它们,无论如何。

我可能会产生一些噪音并给你一些示例代码,但这确实是你必须适应你的应用程序的东西。 (例如,这种方法有一个缺点,即捕获“悬崖边缘”中间点的高原可能会在移除高原的其余部分时离开该点。如果这是你担心的事情,你'在你识别出高原后,你必须做更多的探索。)你应该能够在一次遍历数据的过程中做到这一点,但首先获得整个数据集的一些统计数据以智能地调整你的阈值可能是明智的。

如果您准确定义了高原的构成,您可以减少手摇和机器学习的外观,但只要您尝试识别模糊模式,您将不得不采用基于统计的方法。

【讨论】:

    【解决方案2】:

    有一个方法scipy.signal.find_peaks可以试试,这里有一个例子

    import numpy
    from scipy.signal import find_peaks
    
    test = numpy.random.uniform(0.9, 1.0, 100)
    test[10 : 20] = 0
    peaks, peak_plateaus = find_peaks(- test, plateau_size = 1)
    

    虽然find_peaks只能找到峰值,但是如果数组被取反,它可以用来寻找谷值,那么你可以执行以下操作

    for i in range(len(peak_plateaus['plateau_sizes'])):
        if peak_plateaus['plateau_sizes'][i] > 1:
            print('a plateau of size %d is found' % peak_plateaus['plateau_sizes'][i])
            print('its left index is %d and right index is %d' % (peak_plateaus['left_edges'][i], peak_plateaus['right_edges'][i]))
    

    它会打印出来

    a plateau of size 10 is found
    its left index is 10 and right index is 19
    

    【讨论】:

      【解决方案3】:

      我遇到了类似的问题,并在下面找到了一个简单的启发式解决方案。我发现高原是信号恒定梯度的范围。您可以更改代码以检查渐变是否(接近)0。

      我应用移动平均线 (uniform_filter_1d) 来滤除噪声。另外,我用数字计算信号的一阶和二阶导数,所以我不确定它是否符合效率要求。但它对我的信号非常有效,对其他人来说可能是一个很好的起点。

      def find_plateaus(F, min_length=200, tolerance = 0.75, smoothing=25):
          '''
          Finds plateaus of signal using second derivative of F.
      
          Parameters
          ----------
          F : Signal.
          min_length: Minimum length of plateau.
          tolerance: Number between 0 and 1 indicating how tolerant
              the requirement of constant slope of the plateau is.
          smoothing: Size of uniform filter 1D applied to F and its derivatives.
          
          Returns
          -------
          plateaus: array of plateau left and right edges pairs
          dF: (smoothed) derivative of F
          d2F: (smoothed) Second Derivative of F
          '''
          import numpy as np
          from scipy.ndimage.filters import uniform_filter1d
          
          # calculate smooth gradients
          smoothF = uniform_filter1d(F, size = smoothing)
          dF = uniform_filter1d(np.gradient(smoothF),size = smoothing)
          d2F = uniform_filter1d(np.gradient(dF),size = smoothing)
          
          def zero_runs(x):
              '''
              Helper function for finding sequences of 0s in a signal
              https://stackoverflow.com/questions/24885092/finding-the-consecutive-zeros-in-a-numpy-array/24892274#24892274
              '''
              iszero = np.concatenate(([0], np.equal(x, 0).view(np.int8), [0]))
              absdiff = np.abs(np.diff(iszero))
              ranges = np.where(absdiff == 1)[0].reshape(-1, 2)
              return ranges
          
          # Find ranges where second derivative is zero
          # Values under eps are assumed to be zero.
          eps = np.quantile(abs(d2F),tolerance) 
          smalld2F = (abs(d2F) <= eps)
          
          # Find repititions in the mask "smalld2F" (i.e. ranges where d2F is constantly zero)
          p = zero_runs(np.diff(smalld2F))
          
          # np.diff(p) gives the length of each range found.
          # only accept plateaus of min_length
          plateaus = p[(np.diff(p) > min_length).flatten()]
          
          return (plateaus, dF, d2F)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-02-19
        • 1970-01-01
        • 2017-12-23
        • 2011-09-09
        • 2017-07-18
        • 2015-11-12
        • 2016-08-11
        • 1970-01-01
        相关资源
        最近更新 更多