ChatGPT解决这个技术问题 Extra ChatGPT

实时时间序列数据中的峰值信号检测

更新:目前性能最佳的算法 is this one

这个问题探讨了检测实时时间序列数据中突然峰值的稳健算法。

考虑以下示例数据:

https://i.stack.imgur.com/yUeKr.jpg

此数据的示例采用 Matlab 格式(但此问题不是关于语言,而是关于算法):

p = [1 1 1.1 1 0.9 1 1 1.1 1 0.9 1 1.1 1 1 0.9 1 1 1.1 1 1 1 1 1.1 0.9 1 1.1 1 1 0.9, ...
     1 1.1 1 1 1.1 1 0.8 0.9 1 1.2 0.9 1 1 1.1 1.2 1 1.5 1 3 2 5 3 2 1 1 1 0.9 1 1, ... 
     3 2.6 4 3 3.2 2 1 1 0.8 4 4 2 2.5 1 1 1];

可以清楚地看到,有三个大峰和一些小峰。该数据集是问题所涉及的时间序列数据集类的一个具体示例。此类数据集具有两个一般特征:

存在具有一般平均值的基本噪声 存在明显偏离噪声的大“峰值”或“更高数据点”。

我们还假设以下内容:

峰的宽度无法事先确定

峰高显着偏离其他值

算法实时更新(因此每个新数据点都会更新)

对于这种情况,需要构建触发信号的边界值。然而,边界值不能是静态的,必须基于算法实时确定。

我的问题:什么是实时计算此类阈值的好算法?是否有针对这种情况的特定算法?最知名的算法有哪些?

强大的算法或有用的见解都受到高度赞赏。 (可以用任何语言回答:这是关于算法的)


J
Jean-Paul

强大的峰值检测算法(使用 z 分数)

我想出了一个对这些类型的数据集非常有效的算法。它基于 dispersion 的原理:如果一个新数据点与某个移动均值相差给定 x 个标准差,则算法发出信号(也称为 z-score)。该算法非常稳健,因为它构造了一个分离移动均值和偏差,这样信号就不会破坏阈值。因此,无论先前信号的数量如何,都以大致相同的精度识别未来信号。该算法需要 3 个输入:lag = the lag of the moving windowthreshold = the z-score at which the algorithm signalsinfluence = the influence (between 0 and 1) of new signals on the mean and standard deviation。例如,lag 为 5 将使用最后 5 个观测值来平滑数据。如果数据点与移动平均值相差 3.5 个标准差,则 3.5 的 threshold 将发出信号。 0.5 的 influence 给出了正常数据点影响的 一半 信号。同样,influence 为 0 会完全忽略信号以重新计算新阈值。因此,0 的影响是最稳健的选项(但假设为 stationarity);将影响选项设置为 1 是最不稳健的。因此,对于非平稳数据,影响选项应置于 0 和 1 之间。

它的工作原理如下:

伪代码

# Let y be a vector of timeseries data of at least length lag+2
# Let mean() be a function that calculates the mean
# Let std() be a function that calculates the standard deviaton
# Let absolute() be the absolute value function

# Settings (the ones below are examples: choose what is best for your data)
set lag to 5;          # lag 5 for the smoothing functions
set threshold to 3.5;  # 3.5 standard deviations for signal
set influence to 0.5;  # between 0 and 1, where 1 is normal influence, 0.5 is half

# Initialize variables
set signals to vector 0,...,0 of length of y;   # Initialize signal results
set filteredY to y(1),...,y(lag)                # Initialize filtered series
set avgFilter to null;                          # Initialize average filter
set stdFilter to null;                          # Initialize std. filter
set avgFilter(lag) to mean(y(1),...,y(lag));    # Initialize first value
set stdFilter(lag) to std(y(1),...,y(lag));     # Initialize first value

for i=lag+1,...,t do
  if absolute(y(i) - avgFilter(i-1)) > threshold*stdFilter(i-1) then
    if y(i) > avgFilter(i-1) then
      set signals(i) to +1;                     # Positive signal
    else
      set signals(i) to -1;                     # Negative signal
    end
    set filteredY(i) to influence*y(i) + (1-influence)*filteredY(i-1);
  else
    set signals(i) to 0;                        # No signal
    set filteredY(i) to y(i);
  end
  set avgFilter(i) to mean(filteredY(i-lag+1),...,filteredY(i));
  set stdFilter(i) to std(filteredY(i-lag+1),...,filteredY(i));
end

为您的数据选择好的参数的经验法则可以在下面找到。

演示

https://i.imgur.com/LFvEM2Y.gif

可以在 here 中找到此演示的 Matlab 代码。要使用该演示,只需运行它并通过单击上面的图表自己创建一个时间序列。该算法在绘制 lag 次观察后开始工作。

结果

对于原始问题,当使用以下设置时,此算法将给出以下输出:lag = 30, threshold = 5, influence = 0

https://i.stack.imgur.com/KdpF7.jpg

不同编程语言的实现:

Matlab(我)

R(我)

Golang (Xeoncross)

Golang [高效版] (Micah Parks)

蟒蛇(R Kiselev)

Python【高效版】(delica)

斯威夫特(我)

Groovy (JoshuaCWebDeveloper)

C++(布拉德)

C++ (Animesh Pandey)

铁锈 (swizard)

斯卡拉(迈克·罗伯茨)

Kotlin (leoderprofi)

红宝石 (Kimmo Lehto)

Fortran [用于共振检测] (THo)

朱莉娅(马特坎普)

C#(海洋空投)

C (大卫C)

Java (takanuva15)

JavaScript (德克·吕塞布林克)

打字稿(杰瑞赌博)

Perl (艾伦)

PHP (radhoo)

PHP (gtjamesa)

飞镖(Sga)

配置算法的经验法则

lag:滞后参数决定了您的数据将被平滑多少以及算法对数据长期平均值变化的适应性。 stationary 您的数据越多,您应该包含的滞后越多(这应该会提高算法的稳健性)。如果您的数据包含随时间变化的趋势,您应该考虑您希望算法以多快的速度适应这些趋势。即,如果您将 lag 设置为 10,则需要 10 个“周期”才能将算法的阈值调整为长期平均值的任何系统变化。因此,请根据数据的趋势行为以及您希望算法的适应性选择 lag 参数。

influence:该参数决定了信号对算法检测阈值的影响。如果设置为 0,则信号对阈值没有影响,因此基于阈值检测未来信号,该阈值使用不受过去信号影响的平均值和标准偏差计算。如果设为 0.5,则信号的影响是正常数据点的 一半。另一种思考方式是,如果您将影响设为 0,您就隐含地假设了平稳性(即,无论有多少信号,您总是希望时间序列在长期内恢复到相同的平均值)。如果不是这种情况,则应将影响参数置于 0 和 1 之间,具体取决于信号系统地影响数据随时间变化趋势的程度。例如,如果信号导致时间序列的长期平均值的structural break,则应将影响参数设置为高(接近 1),以便阈值可以快速对结构性中断做出反应。

threshold:阈值参数是移动均值的标准差数,超过该数,算法会将新数据点归类为信号。例如,如果一个新数据点比移动平均值高 4.0 个标准差,并且阈值参数设置为 3.5,则算法会将数据点识别为信号。此参数应根据您期望的信号数量来设置。例如,如果您的数据呈正态分布,则 3.5 的阈值(或:z 分数)对应于 0.00047(来自 this table)的信号概率,这意味着您期望每 2128 个数据点(1/0.00047 )。因此,阈值直接影响算法的敏感程度,从而也决定了算法发出信号的频率。检查您自己的数据并选择一个合理的阈值,使算法在您需要时发出信号(这里可能需要一些反复试验才能达到适合您目的的良好阈值)。

警告:上面的代码每次运行时总是循环遍历所有数据点。 实现此代码时,请确保将信号的计算拆分为单独的函数(没有循环)。然后当一个新的数据点到达时,更新一次 filteredYavgFilterstdFilter。不要在每次有新数据点时重新计算所有数据的信号(如上面的示例),这在实时应用程序中效率极低且速度很慢。

修改算法(潜在改进)的其他方法是:

使用中值而不是平均值 使用稳健的尺度测量,例如中值绝对偏差 (MAD),而不是标准偏差 使用信号余量,因此信号不会频繁切换 更改影响参数的工作方式 处理和向下信号不同(不对称处理)为均值和标准差创建单独的影响参数(如在此 Swift 翻译中)

(已知)对此 StackOverflow 答案的学术引用:

Link, J.、Perst, T.、Stoeve, M. 和 Eskofier, BM (2022)。使用卷积神经网络和迁移学习进行终极飞盘活动识别的可穿戴传感器。传感器,22(7),2560。

Romeiro, JMN, Torres, FTP 和 Pirotti, F. (2021)。使用光谱指数和 SAR 数据评估规定火灾的影响。 Bollettino della società italiana di fotogrammetria e topografia,(2),36-56。

Moore, J.、Goffin, P.、Wiese, J. 和 Meyer, M. (2021)。一种涉及个人数据的访谈方法。关于交互式、移动、可穿戴和无处不在的技术的 ACM 会议记录,5(4),1-28。

Rykov, Y., Thach, TQ, Bojic, I., Christopoulos, G., & Car, J. (2021)。使用可穿戴设备进行抑郁症筛查的数字生物标志物:机器学习建模的横断面研究。 JMIR mHealth 和 uHealth,9(10),e24872。

Hong, Y.、Xin, Y.、Martin, H.、Bucher, D. 和 Raubal, M. (2021)。基于聚类的个人旅行行为变化检测框架。在第 11 届地理信息科学国际会议(GIScience 2021)-第二部分。

Grammenos, A.、Kalyvianaki, E. 和 Pietzuch, P. (2021)。 Pronto:联合任务调度。 arXiv 预印本 arXiv:2104.13429。

Courtial, N. (2020)。 Fusion d'images multimodales pour l'assistance de procédures d'électrophysiologie cardiaque。博士论文,雷恩大学。

Beckman, WF, Jiménez, M. Á. L., Moerland, PD, Westerhoff, HV, & Verschure, PJ (2020)。 4sUDRB 测序用于乳腺癌细胞中的全基因组转录爆发定量。 bioRxiv。

Olkhovskiy, M.、Müllerová, E. 和 Martínek, P. (2020)。使用一维卷积神经网络进行脉冲信号分类。电气工程杂志,71(6),397-405。

高,S.和卡尔德隆,DP(2020)。可靠的翻正反射替代方案,用于评估啮齿动物的觉醒。科学报告,10(1),1-11。

Chen, G. & Dong, W. (2020)。跨技术通信链路上的反应性干扰和攻击缓解。 ACM 传感器网络交易,17(1)。

Takahashi, R.、Fukumoto, M.、Han, C.、Sasatani, T.、Narusue, Y. 和 Kawahara, Y. (2020)。 TelemetRing:使用无源感应遥测的无电池无线环形键盘。在第 33 届 ACM 年度用户界面软件和技术研讨会论文集上(第 1161-1168 页)。

Negus, MJ, Moore, MR, Oliver, JM, Cimpeanu, R. (2020)。液滴撞击弹簧支撑板:分析和模拟。工程数学杂志,128(3)。

尹 C. (2020)。冠状病毒 SARS-CoV-2 基因组中的二核苷酸重复:进化意义。 ArXiv 电子版,可从以下网址访问:https://arxiv.org/pdf/2006.00280.pdf

Esnaola-Gonzalez, I.、Gómez-Omella, M.、Ferreiro, S.、Fernandez, I.、Lázaro, I. 和 García, E. (2020)。增强家禽生产链的物联网平台。传感器,20(6),1549。

高,S.和卡尔德隆,DP(2020)。皮质-运动整合的连续方案校准麻醉苏醒期间的唤醒水平。 bioRxiv。

Cloud, B., Tarien, B., Liu, A., Shedd, T., Lin, X., Hubbard, M., ... & Moore, JK (2019)。用于估计竞争性划船运动学指标的基于智能手机的自适应传感器融合。公共科学图书馆一号,14(12)。

Ceyssens, F., Carmona, MB, Kil, D., Deprez, M., Tooten, E., Nuttin, B., ... & Puers, R. (2019)。使用 0.06 mm² 溶解微针作为插入装置,使用亚细胞横截面探针进行慢性神经记录。传感器和执行器 B:化学,284,第 369-376 页。

Dons, E., Laeremans, M., Orjuela, JP, Avila-Palencia, I., de Nazelle, A., Nieuwenhuijsen, M., ... & Nawrot, T. (2019)。日常生活中最有可能导致空气污染高峰暴露的交通运输:来自 2000 多天的个人监测的证据。大气环境,213, 424-432。

Schaible BJ、Snook KR、Yin J. 等。 (2019)。 2014 年 1 月至 2015 年 4 月,推特对话和英文新闻媒体报道了五个不同国家的脊髓灰质炎。The Permanente Journal,23, 18-181。

利马,B.(2019 年)。使用支持触觉的机器人指尖进行物体表面探索(博士论文,渥太华大学/渥太华大学)。

Lima, BMR, Ramos, LCS, de Oliveira, TEA, da Fonseca, VP, & Petriu, EM (2019)。使用多模式触觉传感器和基于 Z 分数的峰值检测算法进行心率检测。 CMBES 诉讼,42。

Lima, BMR, de Oliveira, TEA, da Fonseca, VP, Zhu, Q., Goubran, M., Groza, VZ, & Petriu, EM(2019 年 6 月)。使用小型多模式触觉传感器进行心率检测。 2019 年 IEEE 国际医学测量和应用研讨会 (MeMeA)(第 1-6 页)。 IEEE。

Ting, C.、Field, R.、Quach, T.、Bauer, T. (2019)。使用基于压缩的分析的广义边界检测。 ICASSP 2019 - 2019 IEEE 声学、语音和信号处理国际会议 (ICASSP),英国布莱顿,第 3522-3526 页。

承运人,EE(2019)。利用压缩求解离散线性系统。伊利诺伊大学厄巴纳-香槟分校博士论文。

Khandakar, A.、Chowdhury, ME、Ahmed, R.、Dhib, A.、Mohammed, M.、Al-Emadi, NA 和 Michelson, D. (2019)。用于监控和控制驾驶员行为以及驾驶时使用手机的便携式系统。传感器,19(7),1563。

Baskozos, G., Dawes, JM, Austin, JS, Antunes-Martins, A., McDermott, L., Clark, AJ, ... & Orengo, C. (2019)。对背根神经节中长链非编码 RNA 表达的综合分析揭示了神经损伤后的细胞类型特异性和失调。疼痛,160(2),463。

Cloud, B.、Tarien, B.、Crawford, R. 和 Moore, J. (2018)。用于估计竞争性划船运动学指标的基于智能手机的自适应传感器融合。 engrXiv 预印本。

扎伊德尔,TJ(2018)。基于细菌的生物传感的电子接口。博士论文,加州大学伯克利分校。

Perkins, P., Heber, S. (2018)。使用基于 Z 分数的峰值检测算法识别核糖体暂停位点。 IEEE 第 8 届生物和医学计算进展国际会议 (ICCABS),ISBN:978-1-5386-8520-4。

Moore, J.、Goffin, P.、Meyer, M.、Lundrigan, P.、Patwari, N.、Sward, K. 和 Wiese, J. (2018)。通过传感、注释和可视化空气质量数据来管理家庭环境。关于交互式、移动、可穿戴和无处不在技术的 ACM 会议记录,2(3),128。

Lo, O.、Buchanan, WJ、Griffiths, P. 和 Macfarlane, R.(2018 年),改进内部威胁检测、安全和通信网络的距离测量方法,卷。 2018 年,文章 ID 5906368。

Apurupa, NV, Singh, P., Chakravarthy, S., & Buduru, AB (2018)。印度公寓用电模式的批判性研究。博士论文,IIIT-德里。

Scirea, M. (2017)。情感音乐生成及其对玩家体验的影响。博士论文,哥本哈根 IT 大学,数字设计。

Scirea, M.、Eklund, P.、Togelius, J. 和 Risi, S. (2017)。 Primal-improv:走向共同进化的音乐即兴创作。计算机科学与电子工程 (CEEC),2017 年(第 172-177 页)。 IEEE。

Catalbas, MC, Cegovnik, T., Sodnik, J. 和 Gulten, A. (2017)。基于眼动的驾驶员疲劳检测,第 10 届电气和电子工程国际会议 (ELECO),第 913-917 页。

使用此答案中的算法的其他作品

Bergamini, E. 和 E. Mourlon-Druol (2021)。谈论欧洲:探索 70 年的新闻档案。工作文件 04/2021,Bruegel。

考克斯,G.(2020)。测量信号中的峰值检测。 https://www.baeldung.com/cs/signal-peak-detection 上的在线文章。

雷蒙多,德国之声(2020)。 SwitP:实时游泳分析的移动应用程序。学期论文,苏黎世联邦理工学院。

Bernardi, D. (2019)。通过多模式手势配对智能手表和移动设备的可行性研究。硕士论文,阿尔托大学。

Lemmens, E. (2018)。使用统计方法检测事件日志中的异常值,硕士论文,埃因霍温大学。

威廉姆斯,P.(2017 年)。老年人情绪控制情感氛围,硕士论文,特温特大学。

Ciocirdel, GD 和 Varga, M. (2016)。基于维基百科页面浏览量的选举预测。项目文件,阿姆斯特丹自由大学。

此答案中算法的其他应用

Avo 审计 dbt 包。 Avo 公司(下一代分析治理)。

使用 OpenBCI 系统合成语音,SarahK01。

Python 包:机器学习金融实验室,基于 De Prado, ML (2018) 的工作。金融机器学习的进展。约翰威利父子公司。

Adafruit CircuitPlayground 图书馆,Adafruit 板(Adafruit Industries)

步数追踪算法,Android App (jeeshnair)

R 包:animaltracker(Joe Champion、Thea Sukianto)

链接到其他峰值检测算法

噪声正弦时间序列中的实时峰值检测

如何引用此算法:

布雷克尔,JPG 货车(2014 年)。 “使用 z 分数的稳健峰值检测算法”。堆栈溢出。位于:https://stackoverflow.com/questions/22583391/peak-signal-detection-in-realtime-timeseries-data/22640362#22640362(版本:2020-11-08)。

Bibtex @misc{brakel2014, author = {Brakel, JPG van}, title = {使用 z-scores 的鲁棒峰值检测算法}, url = {https://stackoverflow.com/questions/22583391/peak-signal-detection-in -realtime-timeseries-data/22640362#22640362},语言 = {en},年份 = {2014},urldate = {2022-04-12},日记 = {Stack Overflow},howpublished = {https://stackoverflow。 com/questions/22583391/peak-signal-detection-in-realtime-timeseries-data/22640362#22640362}}

如果您在某处使用此功能,请使用上述参考来感谢我。如果您对算法有任何疑问,请在下面的评论中发布或通过 LinkedIn 与我联系。


我正在为一些加速度计数据尝试 Matlab 代码,但由于某种原因,threshold 图表在数据中出现高达 20 的大峰值后只是变成了一条平坦的绿线,并且对于图表的其余部分保持不变。 .. 如果我删除了 sike,这不会发生,所以它似乎是由数据中的尖峰引起的。知道会发生什么吗?我是Matlab的新手,所以我无法弄清楚...
有很多方法可以改进这个算法,所以要有创意(向上/向下不同的处理;中值而不是平均值;健壮的标准;将代码编写为内存有效的函数;阈值裕度使信号不会频繁切换等.)。
@datapug 该算法专门设计用于处理实时数据,因此在计算信号时未来值是未知的。你有关于整个时间序列的事前信息吗?在这种情况下,您确实可以反转数据。
@Jean-Paul,是的,现在我明白了.. 我的问题是我试图模拟一个峰值,这导致了一些我无法解释的问题.. 请参见此处:imgur.com/a/GFz59jl 如您所见 - 在信号恢复后原始值 - 标准差保持为 0
@Yitzchak 我明白了。实际上,该算法假定信号的持续时间小于峰值的持续时间。在你的情况下确实是st.dev。将趋于零(因为每个 filteredY(i) = filteredY(i-1))。如果您想让算法适用于您的情况 (influence = 0),一个快速而简单的解决方案是将行 set filteredY(i) to influence*y(i) + (1-influence)*filteredY(i-1); 更改为 set filteredY(i) to filteredY(i-lag)。这样,阈值将简单地回收峰值发生之前的值。请参阅demonstration here
k
krishna chaitanya

这是平滑 z 得分算法的 Python / numpy 实现(参见 answer above)。您可以找到 gist here

#!/usr/bin/env python
# Implementation of algorithm from https://stackoverflow.com/a/22640362/6029703
import numpy as np
import pylab

def thresholding_algo(y, lag, threshold, influence):
    signals = np.zeros(len(y))
    filteredY = np.array(y)
    avgFilter = [0]*len(y)
    stdFilter = [0]*len(y)
    avgFilter[lag - 1] = np.mean(y[0:lag])
    stdFilter[lag - 1] = np.std(y[0:lag])
    for i in range(lag, len(y)):
        if abs(y[i] - avgFilter[i-1]) > threshold * stdFilter [i-1]:
            if y[i] > avgFilter[i-1]:
                signals[i] = 1
            else:
                signals[i] = -1

            filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i-1]
            avgFilter[i] = np.mean(filteredY[(i-lag+1):i+1])
            stdFilter[i] = np.std(filteredY[(i-lag+1):i+1])
        else:
            signals[i] = 0
            filteredY[i] = y[i]
            avgFilter[i] = np.mean(filteredY[(i-lag+1):i+1])
            stdFilter[i] = np.std(filteredY[(i-lag+1):i+1])

    return dict(signals = np.asarray(signals),
                avgFilter = np.asarray(avgFilter),
                stdFilter = np.asarray(stdFilter))

下面是在同一数据集上的测试,它产生与 R/Matlab 的原始答案相同的图

# Data
y = np.array([1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1])

# Settings: lag = 30, threshold = 5, influence = 0
lag = 30
threshold = 5
influence = 0

# Run algo with settings from above
result = thresholding_algo(y, lag=lag, threshold=threshold, influence=influence)

# Plot result
pylab.subplot(211)
pylab.plot(np.arange(1, len(y)+1), y)

pylab.plot(np.arange(1, len(y)+1),
           result["avgFilter"], color="cyan", lw=2)

pylab.plot(np.arange(1, len(y)+1),
           result["avgFilter"] + threshold * result["stdFilter"], color="green", lw=2)

pylab.plot(np.arange(1, len(y)+1),
           result["avgFilter"] - threshold * result["stdFilter"], color="green", lw=2)

pylab.subplot(212)
pylab.step(np.arange(1, len(y)+1), result["signals"], color="red", lw=2)
pylab.ylim(-1.5, 1.5)
pylab.show()

这里的“y”实际上是信号,“信号”是一组数据点,我的理解是否正确?
@TheTank y 是您传入的数据数组,signals+1-1 输出数组,指示每个数据点 y[i] 给定您使用的设置,该数据点是否是“显着峰值”。
a
aha

一种方法是根据以下观察检测峰值:

如果 (y(t) > y(t-1)) && (y(t) > y(t+1)) 时间 t 是一个峰值

它通过等待上升趋势结束来避免误报。它不完全是“实时的”,因为它将错过峰值一 dt。灵敏度可以通过要求比较的余量来控制。在噪声检测和检测时间延迟之间存在折衷。您可以通过添加更多参数来丰富模型:

峰值如果 (y(t) - y(t-dt) > m) && (y(t) - y(t+dt) > m)

其中 dt 和 m 是控制灵敏度与时间延迟的参数

https://i.stack.imgur.com/8a5BP.png

这是在 python 中重现绘图的代码:

import numpy as np
import matplotlib.pyplot as plt
input = np.array([ 1. ,  1. ,  1. ,  1. ,  1. ,  1. ,  1. ,  1.1,  1. ,  0.8,  0.9,
    1. ,  1.2,  0.9,  1. ,  1. ,  1.1,  1.2,  1. ,  1.5,  1. ,  3. ,
    2. ,  5. ,  3. ,  2. ,  1. ,  1. ,  1. ,  0.9,  1. ,  1. ,  3. ,
    2.6,  4. ,  3. ,  3.2,  2. ,  1. ,  1. ,  1. ,  1. ,  1. ])
signal = (input > np.roll(input,1)) & (input > np.roll(input,-1))
plt.plot(input)
plt.plot(signal.nonzero()[0], input[signal], 'ro')
plt.show()

https://i.stack.imgur.com/7XLx7.png


我将如何改变灵敏度?
我可以想到两种方法: 1:将 m 设置为更大的值,以便只检测到更大的峰值。 2:不是计算 y(t) - y(t-dt)(和 y(t) - y(t+dt)),而是从 t-dt 积分到 t(以及 t 到 t+dt)。
您根据什么标准拒绝其他 7 个峰?
平坦的峰值存在问题,因为您所做的基本上是一维边缘检测(例如将信号与 [1 0 -1] 进行卷积)
c
cklin

在信号处理中,峰值检测通常通过小波变换完成。您基本上对时间序列数据进行离散小波变换。返回的细节系数中的过零将对应于时间序列信号中的峰值。您会在不同的细节系数级别上检测到不同的峰值幅度,从而为您提供多级分辨率。


您的回答让我对 this articlethis answer 有帮助,这将帮助我为我的实现构建一个好的算法。谢谢!
m
matan h

适用于实时流的 Python 版本(不会在每个新数据点到达时重新计算所有数据点)。您可能想要调整类函数返回的内容 - 出于我的目的,我只需要信号。

import numpy as np


class real_time_peak_detection():
    def __init__(self, array, lag, threshold, influence):
        self.y = list(array)
        self.length = len(self.y)
        self.lag = lag
        self.threshold = threshold
        self.influence = influence
        self.signals = [0] * len(self.y)
        self.filteredY = np.array(self.y).tolist()
        self.avgFilter = [0] * len(self.y)
        self.stdFilter = [0] * len(self.y)
        self.avgFilter[self.lag - 1] = np.mean(self.y[0:self.lag]).tolist()
        self.stdFilter[self.lag - 1] = np.std(self.y[0:self.lag]).tolist()

    def thresholding_algo(self, new_value):
        self.y.append(new_value)
        i = len(self.y) - 1
        self.length = len(self.y)
        if i < self.lag:
            return 0
        elif i == self.lag:
            self.signals = [0] * len(self.y)
            self.filteredY = np.array(self.y).tolist()
            self.avgFilter = [0] * len(self.y)
            self.stdFilter = [0] * len(self.y)
            self.avgFilter[self.lag] = np.mean(self.y[0:self.lag]).tolist()
            self.stdFilter[self.lag] = np.std(self.y[0:self.lag]).tolist()
            return 0

        self.signals += [0]
        self.filteredY += [0]
        self.avgFilter += [0]
        self.stdFilter += [0]

        if abs(self.y[i] - self.avgFilter[i - 1]) > (self.threshold * self.stdFilter[i - 1]):

            if self.y[i] > self.avgFilter[i - 1]:
                self.signals[i] = 1
            else:
                self.signals[i] = -1

            self.filteredY[i] = self.influence * self.y[i] + \
                (1 - self.influence) * self.filteredY[i - 1]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])
        else:
            self.signals[i] = 0
            self.filteredY[i] = self.y[i]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])

        return self.signals[i]

j
jbm

我们尝试在我们的数据集上使用平滑 z-score 算法,这会导致过度敏感或不敏感(取决于如何调整参数),几乎没有中间立场。在我们网站的交通信号中,我们观察到代表每日周期的低频基线,即使使用最佳参数(如下所示),它仍然落后,尤其是在第 4 天,因为大多数数据点被识别为异常.

在原始 z-score 算法的基础上,我们想出了一种通过反向过滤来解决这个问题的方法。修改后的算法及其在电视商业流量归因中的应用的详细信息发布在 our team blog 上。

https://i.stack.imgur.com/q2uIt.png


很高兴看到该算法是您更高级版本的起点。您的数据具有非常特殊的模式,因此首先使用其他技术删除模式然后将算法应用于残差确实更有意义。或者,您可能希望使用居中而不是滞后窗口来计算平均值/st.dev。另一条评论:您的解决方案从右向左移动以识别尖峰,但这在实时应用程序中是不可能的(这就是为什么原始算法如此简单的原因,因为未来的信息无法访问)。
S
S. Huber

在计算拓扑中,持久同源性的想法导致了一种高效的——像排序数字一样快的——解决方案。它不仅检测峰,它还以自然的方式量化峰的“重要性”,使您可以选择对您有意义的峰。

算法总结。在一维设置(时间序列,实值信号)中,该算法可以很容易地用下图描述:

https://i.stack.imgur.com/gin9Lm.png

将函数图(或其子级别集)视为一个景观,并考虑从无限大(或该图中的 1.8)开始的水位下降。当水平降低时,会在局部最大值出现岛屿。在局部最小值,这些岛屿合并在一起。这个想法的一个细节是,较晚出现的岛屿被合并到更古老的岛屿中。一个岛屿的“持久性”是它的诞生时间减去它的死亡时间。蓝色条的长度描绘了持久性,即上述峰值的“意义”。

效率。在对函数值进行排序之后,找到一个以线性时间运行的实现并不难——实际上它是一个单一的、简单的循环。所以这个实现在实践中应该很快,也很容易实现。

参考资料。 可在此处找到整个故事的描述和对持久同调(计算代数拓扑中的一个领域)动机的参考资料:https://www.sthu.org/blog/13-perstopology-peakdetection/index.html


此算法比例如 scipy.signal.find_peaks 更快、更准确。对于具有 1053896 个数据点的“真实”时间序列,它检测到 137516 个峰值 (13%)。峰的顺序(最重要的在前)允许提取最重要的峰。它提供每个峰值的开始、峰值和结束。适用于嘈杂的数据。
实时数据是指所谓的在线算法,其中数据点一次又一次地接收。峰值的重要性可能由未来的值决定。在不牺牲太多时间复杂度的情况下,通过修改过去的结果将算法扩展为在线会很好。
蓝色条的长度对我来说没有意义。看起来他们总是参考前面的局部最小值,但从不参考下面的局部最小值。在我看来,它们应该同时指代这两者,这意味着 #1 和 3 会更短。
首先,蓝条确实从局部最小值开始。但是,它并不总是下一个局部最小值。事实上,它甚至都不需要成为下一个正确的人。它是在分水岭过程中导致组件合并的原因。在这个特定的现实世界示例中,它似乎只是这样,因为频率响应曲线的性质是它们具有消失的振荡的下降趋势。但是如果你仔细观察#3,那么左边的一个小的局部最小值实际上被跳过了。
我在 C++ 中实现了相同的算法,它比上面链接的 Python 实现快了大约 45 倍。 C++ 实现可用here。享受。
C
Community

原始答案的附录 1:Matlab 和 R 翻译

Matlab代码

function [signals,avgFilter,stdFilter] = ThresholdingAlgo(y,lag,threshold,influence)
% Initialise signal results
signals = zeros(length(y),1);
% Initialise filtered series
filteredY = y(1:lag+1);
% Initialise filters
avgFilter(lag+1,1) = mean(y(1:lag+1));
stdFilter(lag+1,1) = std(y(1:lag+1));
% Loop over all datapoints y(lag+2),...,y(t)
for i=lag+2:length(y)
    % If new value is a specified number of deviations away
    if abs(y(i)-avgFilter(i-1)) > threshold*stdFilter(i-1)
        if y(i) > avgFilter(i-1)
            % Positive signal
            signals(i) = 1;
        else
            % Negative signal
            signals(i) = -1;
        end
        % Make influence lower
        filteredY(i) = influence*y(i)+(1-influence)*filteredY(i-1);
    else
        % No signal
        signals(i) = 0;
        filteredY(i) = y(i);
    end
    % Adjust the filters
    avgFilter(i) = mean(filteredY(i-lag:i));
    stdFilter(i) = std(filteredY(i-lag:i));
end
% Done, now return results
end

例子:

% Data
y = [1 1 1.1 1 0.9 1 1 1.1 1 0.9 1 1.1 1 1 0.9 1 1 1.1 1 1,...
    1 1 1.1 0.9 1 1.1 1 1 0.9 1 1.1 1 1 1.1 1 0.8 0.9 1 1.2 0.9 1,...
    1 1.1 1.2 1 1.5 1 3 2 5 3 2 1 1 1 0.9 1,...
    1 3 2.6 4 3 3.2 2 1 1 0.8 4 4 2 2.5 1 1 1];

% Settings
lag = 30;
threshold = 5;
influence = 0;

% Get results
[signals,avg,dev] = ThresholdingAlgo(y,lag,threshold,influence);

figure; subplot(2,1,1); hold on;
x = 1:length(y); ix = lag+1:length(y);
area(x(ix),avg(ix)+threshold*dev(ix),'FaceColor',[0.9 0.9 0.9],'EdgeColor','none');
area(x(ix),avg(ix)-threshold*dev(ix),'FaceColor',[1 1 1],'EdgeColor','none');
plot(x(ix),avg(ix),'LineWidth',1,'Color','cyan','LineWidth',1.5);
plot(x(ix),avg(ix)+threshold*dev(ix),'LineWidth',1,'Color','green','LineWidth',1.5);
plot(x(ix),avg(ix)-threshold*dev(ix),'LineWidth',1,'Color','green','LineWidth',1.5);
plot(1:length(y),y,'b');
subplot(2,1,2);
stairs(signals,'r','LineWidth',1.5); ylim([-1.5 1.5]);

R代码

ThresholdingAlgo <- function(y,lag,threshold,influence) {
  signals <- rep(0,length(y))
  filteredY <- y[0:lag]
  avgFilter <- NULL
  stdFilter <- NULL
  avgFilter[lag] <- mean(y[0:lag], na.rm=TRUE)
  stdFilter[lag] <- sd(y[0:lag], na.rm=TRUE)
  for (i in (lag+1):length(y)){
    if (abs(y[i]-avgFilter[i-1]) > threshold*stdFilter[i-1]) {
      if (y[i] > avgFilter[i-1]) {
        signals[i] <- 1;
      } else {
        signals[i] <- -1;
      }
      filteredY[i] <- influence*y[i]+(1-influence)*filteredY[i-1]
    } else {
      signals[i] <- 0
      filteredY[i] <- y[i]
    }
    avgFilter[i] <- mean(filteredY[(i-lag):i], na.rm=TRUE)
    stdFilter[i] <- sd(filteredY[(i-lag):i], na.rm=TRUE)
  }
  return(list("signals"=signals,"avgFilter"=avgFilter,"stdFilter"=stdFilter))
}

例子:

# Data
y <- c(1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1)

lag       <- 30
threshold <- 5
influence <- 0

# Run algo with lag = 30, threshold = 5, influence = 0
result <- ThresholdingAlgo(y,lag,threshold,influence)

# Plot result
par(mfrow = c(2,1),oma = c(2,2,0,0) + 0.1,mar = c(0,0,2,1) + 0.2)
plot(1:length(y),y,type="l",ylab="",xlab="") 
lines(1:length(y),result$avgFilter,type="l",col="cyan",lwd=2)
lines(1:length(y),result$avgFilter+threshold*result$stdFilter,type="l",col="green",lwd=2)
lines(1:length(y),result$avgFilter-threshold*result$stdFilter,type="l",col="green",lwd=2)
plot(result$signals,type="S",col="red",ylab="",xlab="",ylim=c(-1.5,1.5),lwd=2)

此代码(两种语言)将为原始问题的数据产生以下结果:

https://i.stack.imgur.com/KdpF7.jpg

原答案附录2:Matlab演示代码

(点击创建数据)

https://i.imgur.com/LFvEM2Y.gif

function [] = RobustThresholdingDemo()

%% SPECIFICATIONS
lag         = 5;       % lag for the smoothing
threshold   = 3.5;     % number of st.dev. away from the mean to signal
influence   = 0.3;     % when signal: how much influence for new data? (between 0 and 1)
                       % 1 is normal influence, 0.5 is half      
%% START DEMO
DemoScreen(30,lag,threshold,influence);

end

function [signals,avgFilter,stdFilter] = ThresholdingAlgo(y,lag,threshold,influence)
signals = zeros(length(y),1);
filteredY = y(1:lag+1);
avgFilter(lag+1,1) = mean(y(1:lag+1));
stdFilter(lag+1,1) = std(y(1:lag+1));
for i=lag+2:length(y)
    if abs(y(i)-avgFilter(i-1)) > threshold*stdFilter(i-1)
        if y(i) > avgFilter(i-1)
            signals(i) = 1;
        else
            signals(i) = -1;
        end
        filteredY(i) = influence*y(i)+(1-influence)*filteredY(i-1);
    else
        signals(i) = 0;
        filteredY(i) = y(i);
    end
    avgFilter(i) = mean(filteredY(i-lag:i));
    stdFilter(i) = std(filteredY(i-lag:i));
end
end

% Demo screen function
function [] = DemoScreen(n,lag,threshold,influence)
figure('Position',[200 100,1000,500]);
subplot(2,1,1);
title(sprintf(['Draw data points (%.0f max)      [settings: lag = %.0f, '...
    'threshold = %.2f, influence = %.2f]'],n,lag,threshold,influence));
ylim([0 5]); xlim([0 50]);
H = gca; subplot(2,1,1);
set(H, 'YLimMode', 'manual'); set(H, 'XLimMode', 'manual');
set(H, 'YLim', get(H,'YLim')); set(H, 'XLim', get(H,'XLim'));
xg = []; yg = [];
for i=1:n
    try
        [xi,yi] = ginput(1);
    catch
        return;
    end
    xg = [xg xi]; yg = [yg yi];
    if i == 1
        subplot(2,1,1); hold on;
        plot(H, xg(i),yg(i),'r.'); 
        text(xg(i),yg(i),num2str(i),'FontSize',7);
    end
    if length(xg) > lag
        [signals,avg,dev] = ...
            ThresholdingAlgo(yg,lag,threshold,influence);
        area(xg(lag+1:end),avg(lag+1:end)+threshold*dev(lag+1:end),...
            'FaceColor',[0.9 0.9 0.9],'EdgeColor','none');
        area(xg(lag+1:end),avg(lag+1:end)-threshold*dev(lag+1:end),...
            'FaceColor',[1 1 1],'EdgeColor','none');
        plot(xg(lag+1:end),avg(lag+1:end),'LineWidth',1,'Color','cyan');
        plot(xg(lag+1:end),avg(lag+1:end)+threshold*dev(lag+1:end),...
            'LineWidth',1,'Color','green');
        plot(xg(lag+1:end),avg(lag+1:end)-threshold*dev(lag+1:end),...
            'LineWidth',1,'Color','green');
        subplot(2,1,2); hold on; title('Signal output');
        stairs(xg(lag+1:end),signals(lag+1:end),'LineWidth',2,'Color','blue');
        ylim([-2 2]); xlim([0 50]); hold off;
    end
    subplot(2,1,1); hold on;
    for j=2:i
        plot(xg([j-1:j]),yg([j-1:j]),'r'); plot(H,xg(j),yg(j),'r.');
        text(xg(j),yg(j),num2str(j),'FontSize',7);
    end
end
end


u
user276648

继@Jean-Paul 提出的解决方案之后,我在 C# 中实现了他的算法

public class ZScoreOutput
{
    public List<double> input;
    public List<int> signals;
    public List<double> avgFilter;
    public List<double> filtered_stddev;
}

public static class ZScore
{
    public static ZScoreOutput StartAlgo(List<double> input, int lag, double threshold, double influence)
    {
        // init variables!
        int[] signals = new int[input.Count];
        double[] filteredY = new List<double>(input).ToArray();
        double[] avgFilter = new double[input.Count];
        double[] stdFilter = new double[input.Count];

        var initialWindow = new List<double>(filteredY).Skip(0).Take(lag).ToList();

        avgFilter[lag - 1] = Mean(initialWindow);
        stdFilter[lag - 1] = StdDev(initialWindow);

        for (int i = lag; i < input.Count; i++)
        {
            if (Math.Abs(input[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1])
            {
                signals[i] = (input[i] > avgFilter[i - 1]) ? 1 : -1;
                filteredY[i] = influence * input[i] + (1 - influence) * filteredY[i - 1];
            }
            else
            {
                signals[i] = 0;
                filteredY[i] = input[i];
            }

            // Update rolling average and deviation
            var slidingWindow = new List<double>(filteredY).Skip(i - lag).Take(lag+1).ToList();

            var tmpMean = Mean(slidingWindow);
            var tmpStdDev = StdDev(slidingWindow);

            avgFilter[i] = Mean(slidingWindow);
            stdFilter[i] = StdDev(slidingWindow);
        }

        // Copy to convenience class 
        var result = new ZScoreOutput();
        result.input = input;
        result.avgFilter       = new List<double>(avgFilter);
        result.signals         = new List<int>(signals);
        result.filtered_stddev = new List<double>(stdFilter);

        return result;
    }

    private static double Mean(List<double> list)
    {
        // Simple helper function! 
        return list.Average();
    }

    private static double StdDev(List<double> values)
    {
        double ret = 0;
        if (values.Count() > 0)
        {
            double avg = values.Average();
            double sum = values.Sum(d => Math.Pow(d - avg, 2));
            ret = Math.Sqrt((sum) / (values.Count() - 1));
        }
        return ret;
    }
}

示例用法:

var input = new List<double> {1.0, 1.0, 1.1, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0, 0.9, 1.0,
    1.1, 1.0, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0, 1.0, 1.0, 1.0, 1.1, 0.9, 1.0, 1.1, 1.0, 1.0, 0.9,
    1.0, 1.1, 1.0, 1.0, 1.1, 1.0, 0.8, 0.9, 1.0, 1.2, 0.9, 1.0, 1.0, 1.1, 1.2, 1.0, 1.5, 1.0,
    3.0, 2.0, 5.0, 3.0, 2.0, 1.0, 1.0, 1.0, 0.9, 1.0, 1.0, 3.0, 2.6, 4.0, 3.0, 3.2, 2.0, 1.0,
    1.0, 0.8, 4.0, 4.0, 2.0, 2.5, 1.0, 1.0, 1.0};

int lag = 30;
double threshold = 5.0;
double influence = 0.0;

var output = ZScore.StartAlgo(input, lag, threshold, influence);

嗨,我认为该代码中存在错误,在您采用 values.Count()-1 的方法 StdDev 中,是否应该依赖-1?我想你会想要项目的数量,这就是你从 values.Count() 中得到的。
嗯..好地方。虽然我最初将该算法移植到 C#,但我从未最终使用它。我可能会用调用 nuget 库 MathNet 来替换整个函数。 "Install-Package MathNet.Numerics" 它具有 PopulationStandardDeviation() 和 StandardDeviation() 的预建函数;例如。 var populationStdDev = new List(1,2,3,4).PopulationStandardDeviation(); var sampleStdDev = new List(1,2,3,4).StandardDeviation();
J
Jean-Paul

发现 Palshikar (2009) 的另一种算法:

Palshikar, G. (2009)。用于时间序列峰值检测的简单算法。在过程中。第一诠释。会议。高级数据分析、业务分析和智能(第 122 卷)。

论文可以下载from here

算法是这样的:

algorithm peak1 // one peak detection algorithms that uses peak function S1 

input T = x1, x2, …, xN, N // input time-series of N points 
input k // window size around the peak 
input h // typically 1 <= h <= 3 
output O // set of peaks detected in T 

begin 
O = empty set // initially empty 

    for (i = 1; i < n; i++) do
        // compute peak function value for each of the N points in T 
        a[i] = S1(k,i,xi,T); 
    end for 

    Compute the mean m' and standard deviation s' of all positive values in array a; 

    for (i = 1; i < n; i++) do // remove local peaks which are “small” in global context 
        if (a[i] > 0 && (a[i] – m') >( h * s')) then O = O + {xi}; 
        end if 
    end for 

    Order peaks in O in terms of increasing index in T 

    // retain only one peak out of any set of peaks within distance k of each other 

    for every adjacent pair of peaks xi and xj in O do 
        if |j – i| <= k then remove the smaller value of {xi, xj} from O 
        end if 
    end for 
end

优点

论文提供了5种不同的峰值检测算法

该算法适用于原始时间序列数据(无需平滑)

缺点

难以事先确定 k 和 h

峰不能平坦(就像我的测试数据中的第三个峰)

例子:

https://i.stack.imgur.com/OqonY.jpg


实际上很有趣的论文。在他看来,S4 似乎是一个更好的功能。但更重要的是澄清 k
上面的链接不起作用。你能修好吗?谢谢。
@smm 修复了链接。
X
Xeoncross

这是 Golang 中 Smoothed z-score 算法(上图)的实现。它假定为 []int16 片(PCM 16 位样本)。您可以找到 gist here

/*
Settings (the ones below are examples: choose what is best for your data)
set lag to 5;          # lag 5 for the smoothing functions
set threshold to 3.5;  # 3.5 standard deviations for signal
set influence to 0.5;  # between 0 and 1, where 1 is normal influence, 0.5 is half
*/

// ZScore on 16bit WAV samples
func ZScore(samples []int16, lag int, threshold float64, influence float64) (signals []int16) {
    //lag := 20
    //threshold := 3.5
    //influence := 0.5

    signals = make([]int16, len(samples))
    filteredY := make([]int16, len(samples))
    for i, sample := range samples[0:lag] {
        filteredY[i] = sample
    }
    avgFilter := make([]int16, len(samples))
    stdFilter := make([]int16, len(samples))

    avgFilter[lag] = Average(samples[0:lag])
    stdFilter[lag] = Std(samples[0:lag])

    for i := lag + 1; i < len(samples); i++ {

        f := float64(samples[i])

        if float64(Abs(samples[i]-avgFilter[i-1])) > threshold*float64(stdFilter[i-1]) {
            if samples[i] > avgFilter[i-1] {
                signals[i] = 1
            } else {
                signals[i] = -1
            }
            filteredY[i] = int16(influence*f + (1-influence)*float64(filteredY[i-1]))
            avgFilter[i] = Average(filteredY[(i - lag):i])
            stdFilter[i] = Std(filteredY[(i - lag):i])
        } else {
            signals[i] = 0
            filteredY[i] = samples[i]
            avgFilter[i] = Average(filteredY[(i - lag):i])
            stdFilter[i] = Std(filteredY[(i - lag):i])
        }
    }

    return
}

// Average a chunk of values
func Average(chunk []int16) (avg int16) {
    var sum int64
    for _, sample := range chunk {
        if sample < 0 {
            sample *= -1
        }
        sum += int64(sample)
    }
    return int16(sum / int64(len(chunk)))
}

@Jean-Paul 我不完全确定一切都是正确的,所以可能存在错误。
您是否尝试过从 Matlab/R 复制演示示例输出?这应该是对质量的良好确认。
另一个使用带有简洁助手的浮点数的 Go 实现:play.golang.org/p/ka0x-QEWeLe
D
DavidC

这是 Arduino 微控制器的 @Jean-Paul's Smoothed Z-score 的 C 实现,用于获取加速度计读数并确定撞击方向是来自左侧还是右侧。这表现得非常好,因为这个设备返回一个反弹的信号。这是来自设备的峰值检测算法的输入 - 显示来自右侧的冲击,然后是来自左侧的冲击。您可以看到初始尖峰,然后是传感器的振荡。

https://i.stack.imgur.com/3Jl2b.png

#include <stdio.h>
#include <math.h>
#include <string.h>


#define SAMPLE_LENGTH 1000

float stddev(float data[], int len);
float mean(float data[], int len);
void thresholding(float y[], int signals[], int lag, float threshold, float influence);


void thresholding(float y[], int signals[], int lag, float threshold, float influence) {
    memset(signals, 0, sizeof(int) * SAMPLE_LENGTH);
    float filteredY[SAMPLE_LENGTH];
    memcpy(filteredY, y, sizeof(float) * SAMPLE_LENGTH);
    float avgFilter[SAMPLE_LENGTH];
    float stdFilter[SAMPLE_LENGTH];

    avgFilter[lag - 1] = mean(y, lag);
    stdFilter[lag - 1] = stddev(y, lag);

    for (int i = lag; i < SAMPLE_LENGTH; i++) {
        if (fabsf(y[i] - avgFilter[i-1]) > threshold * stdFilter[i-1]) {
            if (y[i] > avgFilter[i-1]) {
                signals[i] = 1;
            } else {
                signals[i] = -1;
            }
            filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i-1];
        } else {
            signals[i] = 0;
        }
        avgFilter[i] = mean(filteredY + i-lag, lag);
        stdFilter[i] = stddev(filteredY + i-lag, lag);
    }
}

float mean(float data[], int len) {
    float sum = 0.0, mean = 0.0;

    int i;
    for(i=0; i<len; ++i) {
        sum += data[i];
    }

    mean = sum/len;
    return mean;


}

float stddev(float data[], int len) {
    float the_mean = mean(data, len);
    float standardDeviation = 0.0;

    int i;
    for(i=0; i<len; ++i) {
        standardDeviation += pow(data[i] - the_mean, 2);
    }

    return sqrt(standardDeviation/len);
}

int main() {
    printf("Hello, World!\n");
    int lag = 100;
    float threshold = 5;
    float influence = 0;
    float y[]=  {1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
  ....
1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1}

    int signal[SAMPLE_LENGTH];

    thresholding(y, signal,  lag, threshold, influence);

    return 0;
}

她的影响力= 0的结果

https://i.stack.imgur.com/HGhvw.png

不是很好,但这里有影响力 = 1

https://i.stack.imgur.com/slQEu.png

这是非常好的。


嗨,这是我一年多前写的评论,但没有足够的分数来发布……我对我过去的观察还不是 100% 熟悉,但它就在这里。如果我没有多大意义,我会重新测试它。评论是:“我怀疑当前的实现没有考虑平均和标准差过滤器的直接先前值。例如,滞后 = 5,对于 i = 6,[0,4] 的平均值(包括) 代替 [1,5] (或者可能是 [0,5]?)。我建议将 '(filteredY + i-lag, lag)' 更改为 '(filteredY + i-lag + 1, lag)' ”。
thresholding 函数的第一行,您应该考虑 int 的大小。因此,正确的代码不是 memset(signals, 0, sizeof(float) * SAMPLE_LENGTH),而是 memset(signals, 0, sizeof(int) * SAMPLE_LENGTH)
b
brad

这是平滑 z 分数算法的 C++ 实现 from this answer

std::vector<int> smoothedZScore(std::vector<float> input)
{   
    //lag 5 for the smoothing functions
    int lag = 5;
    //3.5 standard deviations for signal
    float threshold = 3.5;
    //between 0 and 1, where 1 is normal influence, 0.5 is half
    float influence = .5;

    if (input.size() <= lag + 2)
    {
        std::vector<int> emptyVec;
        return emptyVec;
    }

    //Initialise variables
    std::vector<int> signals(input.size(), 0.0);
    std::vector<float> filteredY(input.size(), 0.0);
    std::vector<float> avgFilter(input.size(), 0.0);
    std::vector<float> stdFilter(input.size(), 0.0);
    std::vector<float> subVecStart(input.begin(), input.begin() + lag);
    avgFilter[lag] = mean(subVecStart);
    stdFilter[lag] = stdDev(subVecStart);

    for (size_t i = lag + 1; i < input.size(); i++)
    {
        if (std::abs(input[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1])
        {
            if (input[i] > avgFilter[i - 1])
            {
                signals[i] = 1; //# Positive signal
            }
            else
            {
                signals[i] = -1; //# Negative signal
            }
            //Make influence lower
            filteredY[i] = influence* input[i] + (1 - influence) * filteredY[i - 1];
        }
        else
        {
            signals[i] = 0; //# No signal
            filteredY[i] = input[i];
        }
        //Adjust the filters
        std::vector<float> subVec(filteredY.begin() + i - lag, filteredY.begin() + i);
        avgFilter[i] = mean(subVec);
        stdFilter[i] = stdDev(subVec);
    }
    return signals;
}

警告:此实现实际上并未提供计算均值和标准差的方法。对于 C++11,可以在此处找到一个简单的方法:stackoverflow.com/a/12405793/3250829
t
takanuva15

这是基于之前发布的 Groovy answer 的实际 Java 实现。 (我知道已经发布了 Groovy 和 Kotlin 实现,但是对于像我这样只做过 Java 的人来说,弄清楚如何在其他语言和 Java 之间进行转换真的很麻烦)。

(结果与其他人的图表相符)

算法实现

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.math3.stat.descriptive.SummaryStatistics;

public class SignalDetector {

    public HashMap<String, List> analyzeDataForSignals(List<Double> data, int lag, Double threshold, Double influence) {

        // init stats instance
        SummaryStatistics stats = new SummaryStatistics();

        // the results (peaks, 1 or -1) of our algorithm
        List<Integer> signals = new ArrayList<Integer>(Collections.nCopies(data.size(), 0));

        // filter out the signals (peaks) from our original list (using influence arg)
        List<Double> filteredData = new ArrayList<Double>(data);

        // the current average of the rolling window
        List<Double> avgFilter = new ArrayList<Double>(Collections.nCopies(data.size(), 0.0d));

        // the current standard deviation of the rolling window
        List<Double> stdFilter = new ArrayList<Double>(Collections.nCopies(data.size(), 0.0d));

        // init avgFilter and stdFilter
        for (int i = 0; i < lag; i++) {
            stats.addValue(data.get(i));
        }
        avgFilter.set(lag - 1, stats.getMean());
        stdFilter.set(lag - 1, Math.sqrt(stats.getPopulationVariance())); // getStandardDeviation() uses sample variance
        stats.clear();

        // loop input starting at end of rolling window
        for (int i = lag; i < data.size(); i++) {

            // if the distance between the current value and average is enough standard deviations (threshold) away
            if (Math.abs((data.get(i) - avgFilter.get(i - 1))) > threshold * stdFilter.get(i - 1)) {

                // this is a signal (i.e. peak), determine if it is a positive or negative signal
                if (data.get(i) > avgFilter.get(i - 1)) {
                    signals.set(i, 1);
                } else {
                    signals.set(i, -1);
                }

                // filter this signal out using influence
                filteredData.set(i, (influence * data.get(i)) + ((1 - influence) * filteredData.get(i - 1)));
            } else {
                // ensure this signal remains a zero
                signals.set(i, 0);
                // ensure this value is not filtered
                filteredData.set(i, data.get(i));
            }

            // update rolling average and deviation
            for (int j = i - lag; j < i; j++) {
                stats.addValue(filteredData.get(j));
            }
            avgFilter.set(i, stats.getMean());
            stdFilter.set(i, Math.sqrt(stats.getPopulationVariance()));
            stats.clear();
        }

        HashMap<String, List> returnMap = new HashMap<String, List>();
        returnMap.put("signals", signals);
        returnMap.put("filteredData", filteredData);
        returnMap.put("avgFilter", avgFilter);
        returnMap.put("stdFilter", stdFilter);

        return returnMap;

    } // end
}

主要方法

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;

public class Main {

    public static void main(String[] args) throws Exception {
        DecimalFormat df = new DecimalFormat("#0.000");

        ArrayList<Double> data = new ArrayList<Double>(Arrays.asList(1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d,
                1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d, 1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d,
                1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d, 1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d,
                0.9d, 1d, 1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d));

        SignalDetector signalDetector = new SignalDetector();
        int lag = 30;
        double threshold = 5;
        double influence = 0;

        HashMap<String, List> resultsMap = signalDetector.analyzeDataForSignals(data, lag, threshold, influence);
        // print algorithm params
        System.out.println("lag: " + lag + "\t\tthreshold: " + threshold + "\t\tinfluence: " + influence);

        System.out.println("Data size: " + data.size());
        System.out.println("Signals size: " + resultsMap.get("signals").size());

        // print data
        System.out.print("Data:\t\t");
        for (double d : data) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        // print signals
        System.out.print("Signals:\t");
        List<Integer> signalsList = resultsMap.get("signals");
        for (int i : signalsList) {
            System.out.print(df.format(i) + "\t");
        }
        System.out.println();

        // print filtered data
        System.out.print("Filtered Data:\t");
        List<Double> filteredDataList = resultsMap.get("filteredData");
        for (double d : filteredDataList) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        // print running average
        System.out.print("Avg Filter:\t");
        List<Double> avgFilterList = resultsMap.get("avgFilter");
        for (double d : avgFilterList) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        // print running std
        System.out.print("Std filter:\t");
        List<Double> stdFilterList = resultsMap.get("stdFilter");
        for (double d : stdFilterList) {
            System.out.print(df.format(d) + "\t");
        }
        System.out.println();

        System.out.println();
        for (int i = 0; i < signalsList.size(); i++) {
            if (signalsList.get(i) != 0) {
                System.out.println("Point " + i + " gave signal " + signalsList.get(i));
            }
        }
    }
}

结果

lag: 30     threshold: 5.0      influence: 0.0
Data size: 74
Signals size: 74
Data:           1.000   1.000   1.100   1.000   0.900   1.000   1.000   1.100   1.000   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.000   1.100   1.000   1.000   1.000   1.000   1.100   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.100   1.000   1.000   1.100   1.000   0.800   0.900   1.000   1.200   0.900   1.000   1.000   1.100   1.200   1.000   1.500   1.000   3.000   2.000   5.000   3.000   2.000   1.000   1.000   1.000   0.900   1.000   1.000   3.000   2.600   4.000   3.000   3.200   2.000   1.000   1.000   0.800   4.000   4.000   2.000   2.500   1.000   1.000   1.000   
Signals:        0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   1.000   0.000   1.000   1.000   1.000   1.000   1.000   0.000   0.000   0.000   0.000   0.000   0.000   1.000   1.000   1.000   1.000   1.000   1.000   0.000   0.000   0.000   1.000   1.000   1.000   1.000   0.000   0.000   0.000   
Filtered Data:  1.000   1.000   1.100   1.000   0.900   1.000   1.000   1.100   1.000   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.000   1.100   1.000   1.000   1.000   1.000   1.100   0.900   1.000   1.100   1.000   1.000   0.900   1.000   1.100   1.000   1.000   1.100   1.000   0.800   0.900   1.000   1.200   0.900   1.000   1.000   1.100   1.200   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   0.900   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   1.000   0.800   0.800   0.800   0.800   0.800   1.000   1.000   1.000   
Avg Filter:     0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   1.003   1.003   1.007   1.007   1.003   1.007   1.010   1.003   1.000   0.997   1.003   1.003   1.003   1.000   1.003   1.010   1.013   1.013   1.013   1.010   1.010   1.010   1.010   1.010   1.007   1.010   1.010   1.003   1.003   1.003   1.007   1.007   1.003   1.003   1.003   1.000   1.000   1.007   1.003   0.997   0.983   0.980   0.973   0.973   0.970   
Std filter:     0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.000   0.060   0.060   0.063   0.063   0.060   0.063   0.060   0.071   0.073   0.071   0.080   0.080   0.080   0.077   0.080   0.087   0.085   0.085   0.085   0.083   0.083   0.083   0.083   0.083   0.081   0.079   0.079   0.080   0.080   0.080   0.077   0.077   0.075   0.075   0.075   0.073   0.073   0.063   0.071   0.080   0.078   0.083   0.089   0.089   0.086   

Point 45 gave signal 1
Point 47 gave signal 1
Point 48 gave signal 1
Point 49 gave signal 1
Point 50 gave signal 1
Point 51 gave signal 1
Point 58 gave signal 1
Point 59 gave signal 1
Point 60 gave signal 1
Point 61 gave signal 1
Point 62 gave signal 1
Point 63 gave signal 1
Point 67 gave signal 1
Point 68 gave signal 1
Point 69 gave signal 1
Point 70 gave signal 1

https://i.stack.imgur.com/sJqrv.png?s=512


当您添加数据而不是列表时,只为流式数据一一添加呢?
@CT我还没有测试过,但看起来你每次得到一个新值时都必须在 for (int i = lag... 循环中运行这些东西。您可以查看 delica's answer,获取 Python 中的实时流式传输示例以获取灵感。
A
Animesh Pandey

C++ 实现

#include <iostream>
#include <vector>
#include <algorithm>
#include <unordered_map>
#include <cmath>
#include <iterator>
#include <numeric>

using namespace std;

typedef long double ld;
typedef unsigned int uint;
typedef std::vector<ld>::iterator vec_iter_ld;

/**
 * Overriding the ostream operator for pretty printing vectors.
 */
template<typename T>
std::ostream &operator<<(std::ostream &os, std::vector<T> vec) {
    os << "[";
    if (vec.size() != 0) {
        std::copy(vec.begin(), vec.end() - 1, std::ostream_iterator<T>(os, " "));
        os << vec.back();
    }
    os << "]";
    return os;
}

/**
 * This class calculates mean and standard deviation of a subvector.
 * This is basically stats computation of a subvector of a window size qual to "lag".
 */
class VectorStats {
public:
    /**
     * Constructor for VectorStats class.
     *
     * @param start - This is the iterator position of the start of the window,
     * @param end   - This is the iterator position of the end of the window,
     */
    VectorStats(vec_iter_ld start, vec_iter_ld end) {
        this->start = start;
        this->end = end;
        this->compute();
    }

    /**
     * This method calculates the mean and standard deviation using STL function.
     * This is the Two-Pass implementation of the Mean & Variance calculation.
     */
    void compute() {
        ld sum = std::accumulate(start, end, 0.0);
        uint slice_size = std::distance(start, end);
        ld mean = sum / slice_size;
        std::vector<ld> diff(slice_size);
        std::transform(start, end, diff.begin(), [mean](ld x) { return x - mean; });
        ld sq_sum = std::inner_product(diff.begin(), diff.end(), diff.begin(), 0.0);
        ld std_dev = std::sqrt(sq_sum / slice_size);

        this->m1 = mean;
        this->m2 = std_dev;
    }

    ld mean() {
        return m1;
    }

    ld standard_deviation() {
        return m2;
    }

private:
    vec_iter_ld start;
    vec_iter_ld end;
    ld m1;
    ld m2;
};

/**
 * This is the implementation of the Smoothed Z-Score Algorithm.
 * This is direction translation of https://stackoverflow.com/a/22640362/1461896.
 *
 * @param input - input signal
 * @param lag - the lag of the moving window
 * @param threshold - the z-score at which the algorithm signals
 * @param influence - the influence (between 0 and 1) of new signals on the mean and standard deviation
 * @return a hashmap containing the filtered signal and corresponding mean and standard deviation.
 */
unordered_map<string, vector<ld>> z_score_thresholding(vector<ld> input, int lag, ld threshold, ld influence) {
    unordered_map<string, vector<ld>> output;

    uint n = (uint) input.size();
    vector<ld> signals(input.size());
    vector<ld> filtered_input(input.begin(), input.end());
    vector<ld> filtered_mean(input.size());
    vector<ld> filtered_stddev(input.size());

    VectorStats lag_subvector_stats(input.begin(), input.begin() + lag);
    filtered_mean[lag - 1] = lag_subvector_stats.mean();
    filtered_stddev[lag - 1] = lag_subvector_stats.standard_deviation();

    for (int i = lag; i < n; i++) {
        if (abs(input[i] - filtered_mean[i - 1]) > threshold * filtered_stddev[i - 1]) {
            signals[i] = (input[i] > filtered_mean[i - 1]) ? 1.0 : -1.0;
            filtered_input[i] = influence * input[i] + (1 - influence) * filtered_input[i - 1];
        } else {
            signals[i] = 0.0;
            filtered_input[i] = input[i];
        }
        VectorStats lag_subvector_stats(filtered_input.begin() + (i - lag), filtered_input.begin() + i);
        filtered_mean[i] = lag_subvector_stats.mean();
        filtered_stddev[i] = lag_subvector_stats.standard_deviation();
    }

    output["signals"] = signals;
    output["filtered_mean"] = filtered_mean;
    output["filtered_stddev"] = filtered_stddev;

    return output;
};

int main() {
    vector<ld> input = {1.0, 1.0, 1.1, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0, 0.9, 1.0, 1.1, 1.0, 1.0, 0.9, 1.0, 1.0, 1.1, 1.0,
                        1.0, 1.0, 1.0, 1.1, 0.9, 1.0, 1.1, 1.0, 1.0, 0.9, 1.0, 1.1, 1.0, 1.0, 1.1, 1.0, 0.8, 0.9, 1.0,
                        1.2, 0.9, 1.0, 1.0, 1.1, 1.2, 1.0, 1.5, 1.0, 3.0, 2.0, 5.0, 3.0, 2.0, 1.0, 1.0, 1.0, 0.9, 1.0,
                        1.0, 3.0, 2.6, 4.0, 3.0, 3.2, 2.0, 1.0, 1.0, 0.8, 4.0, 4.0, 2.0, 2.5, 1.0, 1.0, 1.0};

    int lag = 30;
    ld threshold = 5.0;
    ld influence = 0.0;
    unordered_map<string, vector<ld>> output = z_score_thresholding(input, lag, threshold, influence);
    cout << output["signals"] << endl;
}

P
Peter G

这个问题看起来类似于我在混合/嵌入式系统课程中遇到的问题,但这与传感器输入有噪声时检测故障有关。我们使用 Kalman filter 来估计/预测系统的隐藏状态,然后使用 statistical analysis to determine the likelihood that a fault had occurred。我们正在使用线性系统,但存在非线性变体。我记得这种方法具有惊人的适应性,但它需要一个系统动力学模型。


卡尔曼滤波器很有趣,但我似乎找不到适合我目的的适用算法。不过,我非常感谢您的回答,我将研究一些峰值检测论文like this one,看看我是否可以从任何算法中学习。谢谢!
M
Matt Camp

以为我会为其他人提供算法的 Julia 实现。可以找到要点here

using Statistics
using Plots
function SmoothedZscoreAlgo(y, lag, threshold, influence)
    # Julia implimentation of http://stackoverflow.com/a/22640362/6029703
    n = length(y)
    signals = zeros(n) # init signal results
    filteredY = copy(y) # init filtered series
    avgFilter = zeros(n) # init average filter
    stdFilter = zeros(n) # init std filter
    avgFilter[lag - 1] = mean(y[1:lag]) # init first value
    stdFilter[lag - 1] = std(y[1:lag]) # init first value

    for i in range(lag, stop=n-1)
        if abs(y[i] - avgFilter[i-1]) > threshold*stdFilter[i-1]
            if y[i] > avgFilter[i-1]
                signals[i] += 1 # postive signal
            else
                signals[i] += -1 # negative signal
            end
            # Make influence lower
            filteredY[i] = influence*y[i] + (1-influence)*filteredY[i-1]
        else
            signals[i] = 0
            filteredY[i] = y[i]
        end
        avgFilter[i] = mean(filteredY[i-lag+1:i])
        stdFilter[i] = std(filteredY[i-lag+1:i])
    end
    return (signals = signals, avgFilter = avgFilter, stdFilter = stdFilter)
end


# Data
y = [1,1,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,1,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1]

# Settings: lag = 30, threshold = 5, influence = 0
lag = 30
threshold = 5
influence = 0

results = SmoothedZscoreAlgo(y, lag, threshold, influence)
upper_bound = results[:avgFilter] + threshold * results[:stdFilter]
lower_bound = results[:avgFilter] - threshold * results[:stdFilter]
x = 1:length(y)

yplot = plot(x,y,color="blue", label="Y",legend=:topleft)
yplot = plot!(x,upper_bound, color="green", label="Upper Bound",legend=:topleft)
yplot = plot!(x,results[:avgFilter], color="cyan", label="Average Filter",legend=:topleft)
yplot = plot!(x,lower_bound, color="green", label="Lower Bound",legend=:topleft)
signalplot = plot(x,results[:signals],color="red",label="Signals",legend=:topleft)
plot(yplot,signalplot,layout=(2,1),legend=:topleft)

https://i.stack.imgur.com/UolMY.png


K
Kimmo Lehto

这是我从接受的答案中为“平滑 z 分数算法”创建 Ruby 解决方案的尝试:

module ThresholdingAlgoMixin
  def mean(array)
    array.reduce(&:+) / array.size.to_f
  end

  def stddev(array)
    array_mean = mean(array)
    Math.sqrt(array.reduce(0.0) { |a, b| a.to_f + ((b.to_f - array_mean) ** 2) } / array.size.to_f)
  end

  def thresholding_algo(lag: 5, threshold: 3.5, influence: 0.5)
    return nil if size < lag * 2
    Array.new(size, 0).tap do |signals|
      filtered = Array.new(self)

      initial_slice = take(lag)
      avg_filter = Array.new(lag - 1, 0.0) + [mean(initial_slice)]
      std_filter = Array.new(lag - 1, 0.0) + [stddev(initial_slice)]
      (lag..size-1).each do |idx|
        prev = idx - 1
        if (fetch(idx) - avg_filter[prev]).abs > threshold * std_filter[prev]
          signals[idx] = fetch(idx) > avg_filter[prev] ? 1 : -1
          filtered[idx] = (influence * fetch(idx)) + ((1-influence) * filtered[prev])
        end

        filtered_slice = filtered[idx-lag..prev]
        avg_filter[idx] = mean(filtered_slice)
        std_filter[idx] = stddev(filtered_slice)
      end
    end
  end
end

以及示例用法:

test_data = [
  1, 1, 1.1, 1, 0.9, 1, 1, 1.1, 1, 0.9, 1, 1.1, 1, 1, 0.9, 1,
  1, 1.1, 1, 1, 1, 1, 1.1, 0.9, 1, 1.1, 1, 1, 0.9, 1, 1.1, 1,
  1, 1.1, 1, 0.8, 0.9, 1, 1.2, 0.9, 1, 1, 1.1, 1.2, 1, 1.5,
  1, 3, 2, 5, 3, 2, 1, 1, 1, 0.9, 1, 1, 3, 2.6, 4, 3, 3.2, 2,
  1, 1, 0.8, 4, 4, 2, 2.5, 1, 1, 1
].extend(ThresholdingAlgoMixin)

puts test_data.thresholding_algo.inspect

# Output: [
#   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
#   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0,
#   0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
#   1, 1, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0
# ]

T
THo

这是更改后的 Fortran 版本 of the z-score algorithm。它专门针对频率空间中传递函数中的峰值(共振)检测进行了更改(每个更改在代码中都有一个小注释)。

如果在输入向量的下限附近存在共振,则第一个修改会向用户发出警告,由高于某个阈值(在本例中为 10%)的标准偏差指示。这仅仅意味着信号不够平坦,无法正确初始化滤波器的检测。

第二个修改是只将峰值的最大值添加到找到的峰值中。这是通过将每个找到的峰值与其(滞后)前任及其(滞后)后继的幅度进行比较来实现的。

第三个变化是考虑到共振峰通常在共振频率周围表现出某种形式的对称性。因此,围绕当前数据点对称计算均值和标准差是很自然的(而不仅仅是针对前辈)。这导致更好的峰值检测行为。

修改的效果是,函数必须事先知道整个信号,这是共振检测的常见情况(类似于 Jean-Paul 的 Matlab 示例,其中动态生成数据点将不起作用)。

function PeakDetect(y,lag,threshold, influence)
    implicit none
    ! Declaring part
    real, dimension(:), intent(in) :: y
    integer, dimension(size(y)) :: PeakDetect
    real, dimension(size(y)) :: filteredY, avgFilter, stdFilter
    integer :: lag, ii
    real :: threshold, influence

    ! Executing part
    PeakDetect = 0
    filteredY = 0.0
    filteredY(1:lag+1) = y(1:lag+1)
    avgFilter = 0.0
    avgFilter(lag+1) = mean(y(1:2*lag+1))
    stdFilter = 0.0
    stdFilter(lag+1) = std(y(1:2*lag+1))

    if (stdFilter(lag+1)/avgFilter(lag+1)>0.1) then ! If the coefficient of variation exceeds 10%, the signal is too uneven at the start, possibly because of a peak.
        write(unit=*,fmt=1001)
1001        format(1X,'Warning: Peak detection might have failed, as there may be a peak at the edge of the frequency range.',/)
    end if
    do ii = lag+2, size(y)
        if (abs(y(ii) - avgFilter(ii-1)) > threshold * stdFilter(ii-1)) then
            ! Find only the largest outstanding value which is only the one greater than its predecessor and its successor
            if (y(ii) > avgFilter(ii-1) .AND. y(ii) > y(ii-1) .AND. y(ii) > y(ii+1)) then
                PeakDetect(ii) = 1
            end if
            filteredY(ii) = influence * y(ii) + (1 - influence) * filteredY(ii-1)
        else
            filteredY(ii) = y(ii)
        end if
        ! Modified with respect to the original code. Mean and standard deviation are calculted symmetrically around the current point
        avgFilter(ii) = mean(filteredY(ii-lag:ii+lag))
        stdFilter(ii) = std(filteredY(ii-lag:ii+lag))
    end do
end function PeakDetect

real function mean(y)
    !> @brief Calculates the mean of vector y
    implicit none
    ! Declaring part
    real, dimension(:), intent(in) :: y
    integer :: N
    ! Executing part
    N = max(1,size(y))
    mean = sum(y)/N
end function mean

real function std(y)
    !> @brief Calculates the standard deviation of vector y
    implicit none
    ! Declaring part
    real, dimension(:), intent(in) :: y
    integer :: N
    ! Executing part
    N = max(1,size(y))
    std = sqrt((N*dot_product(y,y) - sum(y)**2) / (N*(N-1)))
end function std

https://i.stack.imgur.com/1s8QJ.png


T
Tranfer Will

答案 https://stackoverflow.com/a/22640362/6029703 在 python/numpy 中的迭代版本在这里。此代码比计算大数据(100000+)的平均偏差和标准偏差更快。

def peak_detection_smoothed_zscore_v2(x, lag, threshold, influence):
    '''
    iterative smoothed z-score algorithm
    Implementation of algorithm from https://stackoverflow.com/a/22640362/6029703
    '''
    import numpy as np
    labels = np.zeros(len(x))
    filtered_y = np.array(x)
    avg_filter = np.zeros(len(x))
    std_filter = np.zeros(len(x))
    var_filter = np.zeros(len(x))

    avg_filter[lag - 1] = np.mean(x[0:lag])
    std_filter[lag - 1] = np.std(x[0:lag])
    var_filter[lag - 1] = np.var(x[0:lag])
    for i in range(lag, len(x)):
        if abs(x[i] - avg_filter[i - 1]) > threshold * std_filter[i - 1]:
            if x[i] > avg_filter[i - 1]:
                labels[i] = 1
            else:
                labels[i] = -1
            filtered_y[i] = influence * x[i] + (1 - influence) * filtered_y[i - 1]
        else:
            labels[i] = 0
            filtered_y[i] = x[i]
        # update avg, var, std
        avg_filter[i] = avg_filter[i - 1] + 1. / lag * (filtered_y[i] - filtered_y[i - lag])
        var_filter[i] = var_filter[i - 1] + 1. / lag * ((filtered_y[i] - avg_filter[i - 1]) ** 2 - (
            filtered_y[i - lag] - avg_filter[i - 1]) ** 2 - (filtered_y[i] - filtered_y[i - lag]) ** 2 / lag)
        std_filter[i] = np.sqrt(var_filter[i])

    return dict(signals=labels,
                avgFilter=avg_filter,
                stdFilter=std_filter)

J
JoshuaCWebDeveloper

这是平滑 z 分数算法 (see answer above) 的 Groovy (Java) 实现。

/**
 * "Smoothed zero-score alogrithm" shamelessly copied from https://stackoverflow.com/a/22640362/6029703
 *  Uses a rolling mean and a rolling deviation (separate) to identify peaks in a vector
 *
 * @param y - The input vector to analyze
 * @param lag - The lag of the moving window (i.e. how big the window is)
 * @param threshold - The z-score at which the algorithm signals (i.e. how many standard deviations away from the moving mean a peak (or signal) is)
 * @param influence - The influence (between 0 and 1) of new signals on the mean and standard deviation (how much a peak (or signal) should affect other values near it)
 * @return - The calculated averages (avgFilter) and deviations (stdFilter), and the signals (signals)
 */

public HashMap<String, List<Object>> thresholdingAlgo(List<Double> y, Long lag, Double threshold, Double influence) {
    //init stats instance
    SummaryStatistics stats = new SummaryStatistics()

    //the results (peaks, 1 or -1) of our algorithm
    List<Integer> signals = new ArrayList<Integer>(Collections.nCopies(y.size(), 0))
    //filter out the signals (peaks) from our original list (using influence arg)
    List<Double> filteredY = new ArrayList<Double>(y)
    //the current average of the rolling window
    List<Double> avgFilter = new ArrayList<Double>(Collections.nCopies(y.size(), 0.0d))
    //the current standard deviation of the rolling window
    List<Double> stdFilter = new ArrayList<Double>(Collections.nCopies(y.size(), 0.0d))
    //init avgFilter and stdFilter
    (0..lag-1).each { stats.addValue(y[it as int]) }
    avgFilter[lag - 1 as int] = stats.getMean()
    stdFilter[lag - 1 as int] = Math.sqrt(stats.getPopulationVariance()) //getStandardDeviation() uses sample variance (not what we want)
    stats.clear()
    //loop input starting at end of rolling window
    (lag..y.size()-1).each { i ->
        //if the distance between the current value and average is enough standard deviations (threshold) away
        if (Math.abs((y[i as int] - avgFilter[i - 1 as int]) as Double) > threshold * stdFilter[i - 1 as int]) {
            //this is a signal (i.e. peak), determine if it is a positive or negative signal
            signals[i as int] = (y[i as int] > avgFilter[i - 1 as int]) ? 1 : -1
            //filter this signal out using influence
            filteredY[i as int] = (influence * y[i as int]) + ((1-influence) * filteredY[i - 1 as int])
        } else {
            //ensure this signal remains a zero
            signals[i as int] = 0
            //ensure this value is not filtered
            filteredY[i as int] = y[i as int]
        }
        //update rolling average and deviation
        (i - lag..i-1).each { stats.addValue(filteredY[it as int] as Double) }
        avgFilter[i as int] = stats.getMean()
        stdFilter[i as int] = Math.sqrt(stats.getPopulationVariance()) //getStandardDeviation() uses sample variance (not what we want)
        stats.clear()
    }

    return [
        signals  : signals,
        avgFilter: avgFilter,
        stdFilter: stdFilter
    ]
}

下面是在同一数据集上进行的测试,产生与 above Python / numpy implementation 相同的结果。

    // Data
    def y = [1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d,
         1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d,
         1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d, 0.9d, 1d,
         1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d]

    // Settings
    def lag = 30
    def threshold = 5
    def influence = 0


    def thresholdingResults = thresholdingAlgo((List<Double>) y, (Long) lag, (Double) threshold, (Double) influence)

    println y.size()
    println thresholdingResults.signals.size()
    println thresholdingResults.signals

    thresholdingResults.signals.eachWithIndex { x, idx ->
        if (x) {
            println y[idx]
        }
    }

M
Mike Roberts

这是 smoothed z-score algorithm 的(非惯用)Scala 版本:

/**
  * Smoothed zero-score alogrithm shamelessly copied from https://stackoverflow.com/a/22640362/6029703
  * Uses a rolling mean and a rolling deviation (separate) to identify peaks in a vector
  *
  * @param y - The input vector to analyze
  * @param lag - The lag of the moving window (i.e. how big the window is)
  * @param threshold - The z-score at which the algorithm signals (i.e. how many standard deviations away from the moving mean a peak (or signal) is)
  * @param influence - The influence (between 0 and 1) of new signals on the mean and standard deviation (how much a peak (or signal) should affect other values near it)
  * @return - The calculated averages (avgFilter) and deviations (stdFilter), and the signals (signals)
  */
private def smoothedZScore(y: Seq[Double], lag: Int, threshold: Double, influence: Double): Seq[Int] = {
  val stats = new SummaryStatistics()

  // the results (peaks, 1 or -1) of our algorithm
  val signals = mutable.ArrayBuffer.fill(y.length)(0)

  // filter out the signals (peaks) from our original list (using influence arg)
  val filteredY = y.to[mutable.ArrayBuffer]

  // the current average of the rolling window
  val avgFilter = mutable.ArrayBuffer.fill(y.length)(0d)

  // the current standard deviation of the rolling window
  val stdFilter = mutable.ArrayBuffer.fill(y.length)(0d)

  // init avgFilter and stdFilter
  y.take(lag).foreach(s => stats.addValue(s))

  avgFilter(lag - 1) = stats.getMean
  stdFilter(lag - 1) = Math.sqrt(stats.getPopulationVariance) // getStandardDeviation() uses sample variance (not what we want)

  // loop input starting at end of rolling window
  y.zipWithIndex.slice(lag, y.length - 1).foreach {
    case (s: Double, i: Int) =>
      // if the distance between the current value and average is enough standard deviations (threshold) away
      if (Math.abs(s - avgFilter(i - 1)) > threshold * stdFilter(i - 1)) {
        // this is a signal (i.e. peak), determine if it is a positive or negative signal
        signals(i) = if (s > avgFilter(i - 1)) 1 else -1
        // filter this signal out using influence
        filteredY(i) = (influence * s) + ((1 - influence) * filteredY(i - 1))
      } else {
        // ensure this signal remains a zero
        signals(i) = 0
        // ensure this value is not filtered
        filteredY(i) = s
      }

      // update rolling average and deviation
      stats.clear()
      filteredY.slice(i - lag, i).foreach(s => stats.addValue(s))
      avgFilter(i) = stats.getMean
      stdFilter(i) = Math.sqrt(stats.getPopulationVariance) // getStandardDeviation() uses sample variance (not what we want)
  }

  println(y.length)
  println(signals.length)
  println(signals)

  signals.zipWithIndex.foreach {
    case(x: Int, idx: Int) =>
      if (x == 1) {
        println(idx + " " + y(idx))
      }
  }

  val data =
    y.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> s, "name" -> "y", "row" -> "data") } ++
    avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> s, "name" -> "avgFilter", "row" -> "data") } ++
    avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> (s - threshold * stdFilter(i)), "name" -> "lower", "row" -> "data") } ++
    avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> (s + threshold * stdFilter(i)), "name" -> "upper", "row" -> "data") } ++
    signals.zipWithIndex.map { case (s: Int, i: Int) => Map("x" -> i, "y" -> s, "name" -> "signal", "row" -> "signal") }

  Vegas("Smoothed Z")
    .withData(data)
    .mark(Line)
    .encodeX("x", Quant)
    .encodeY("y", Quant)
    .encodeColor(
      field="name",
      dataType=Nominal
    )
    .encodeRow("row", Ordinal)
    .show

  return signals
}

这是一个返回与 Python 和 Groovy 版本相同的结果的测试:

val y = List(1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d,
  1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d,
  1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d, 0.9d, 1d,
  1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d)

val lag = 30
val threshold = 5d
val influence = 0d

smoothedZScore(y, lag, threshold, influence)

https://i.stack.imgur.com/CEbYw.png

Gist here


你好!感谢您编写此版本的 Scala 版本!不过我发现了一个小错误。 slice() 函数中似乎不需要 y.length-1 。它会导致最后一个元素被跳过。 gist.github.com/ecopoesis/… 。我通过在各处散布日志语句发现了这一点并注意到了这一点。
感谢您提供此解决方案@MikeRoberts。请更新以提及您需要将 org.apache.commons.math3.stat.descriptive.SummaryStatistics 作为外部依赖项导入。
l
leonardkraemer

我在我的android项目中需要这样的东西。以为我可能会回馈 Kotlin 的实现。

/**
* Smoothed zero-score alogrithm shamelessly copied from https://stackoverflow.com/a/22640362/6029703
* Uses a rolling mean and a rolling deviation (separate) to identify peaks in a vector
*
* @param y - The input vector to analyze
* @param lag - The lag of the moving window (i.e. how big the window is)
* @param threshold - The z-score at which the algorithm signals (i.e. how many standard deviations away from the moving mean a peak (or signal) is)
* @param influence - The influence (between 0 and 1) of new signals on the mean and standard deviation (how much a peak (or signal) should affect other values near it)
* @return - The calculated averages (avgFilter) and deviations (stdFilter), and the signals (signals)
*/
fun smoothedZScore(y: List<Double>, lag: Int, threshold: Double, influence: Double): Triple<List<Int>, List<Double>, List<Double>> {
    val stats = SummaryStatistics()
    // the results (peaks, 1 or -1) of our algorithm
    val signals = MutableList<Int>(y.size, { 0 })
    // filter out the signals (peaks) from our original list (using influence arg)
    val filteredY = ArrayList<Double>(y)
    // the current average of the rolling window
    val avgFilter = MutableList<Double>(y.size, { 0.0 })
    // the current standard deviation of the rolling window
    val stdFilter = MutableList<Double>(y.size, { 0.0 })
    // init avgFilter and stdFilter
    y.take(lag).forEach { s -> stats.addValue(s) }
    avgFilter[lag - 1] = stats.mean
    stdFilter[lag - 1] = Math.sqrt(stats.populationVariance) // getStandardDeviation() uses sample variance (not what we want)
    stats.clear()
    //loop input starting at end of rolling window
    (lag..y.size - 1).forEach { i ->
        //if the distance between the current value and average is enough standard deviations (threshold) away
        if (Math.abs(y[i] - avgFilter[i - 1]) > threshold * stdFilter[i - 1]) {
            //this is a signal (i.e. peak), determine if it is a positive or negative signal
            signals[i] = if (y[i] > avgFilter[i - 1]) 1 else -1
            //filter this signal out using influence
            filteredY[i] = (influence * y[i]) + ((1 - influence) * filteredY[i - 1])
        } else {
            //ensure this signal remains a zero
            signals[i] = 0
            //ensure this value is not filtered
            filteredY[i] = y[i]
        }
        //update rolling average and deviation
        (i - lag..i - 1).forEach { stats.addValue(filteredY[it]) }
        avgFilter[i] = stats.getMean()
        stdFilter[i] = Math.sqrt(stats.getPopulationVariance()) //getStandardDeviation() uses sample variance (not what we want)
        stats.clear()
    }
    return Triple(signals, avgFilter, stdFilter)
}

带有验证图的示例项目可以在 github 找到。

https://i.stack.imgur.com/YhJZSm.png


O
Ocean Airdrop

如果您在数据库表中获取数据,以下是简单 z-score 算法的 SQL 版本:

with data_with_zscore as (
    select
        date_time,
        value,
        value / (avg(value) over ()) as pct_of_mean,
        (value - avg(value) over ()) / (stdev(value) over ()) as z_score
    from {{tablename}}  where datetime > '2018-11-26' and datetime < '2018-12-03'
)


-- select all
select * from data_with_zscore 

-- select only points greater than a certain threshold
select * from data_with_zscore where z_score > abs(2)

你的代码做的不是我提出的算法。您的查询只是计算 z 分数([数据点 - 平均值]/标准),但没有包含我的算法的逻辑,即在计算新的信号阈值时忽略过去的信号。您还忽略了三个参数(滞后、影响、阈值)。您能否修改您的答案以纳入实际逻辑?
是的,你说的没错。起初我以为我可以摆脱上述简化版本。从那以后,我采用了您的完整解决方案并将其移植到 C#。请看下面我的回答。当我有更多时间时,我将重新访问此 SQL 版本并合并您的算法。顺便说一句,感谢您提供如此出色的答案和视觉解释。
C
Community

我允许自己创建它的 javascript 版本。可能会有所帮助。 javascript 应该是上面给出的伪代码的直接转录。作为 npm 包和 github 存储库提供:

https://github.com/crux/smoothed-z-score

@joe_six/smoothed-z-score-peak-signal-detection

Javascript 翻译:

// javascript port of: https://stackoverflow.com/questions/22583391/peak-signal-detection-in-realtime-timeseries-data/48895639#48895639

function sum(a) {
    return a.reduce((acc, val) => acc + val)
}

function mean(a) {
    return sum(a) / a.length
}

function stddev(arr) {
    const arr_mean = mean(arr)
    const r = function(acc, val) {
        return acc + ((val - arr_mean) * (val - arr_mean))
    }
    return Math.sqrt(arr.reduce(r, 0.0) / arr.length)
}

function smoothed_z_score(y, params) {
    var p = params || {}
    // init cooefficients
    const lag = p.lag || 5
    const threshold = p.threshold || 3.5
    const influence = p.influece || 0.5

    if (y === undefined || y.length < lag + 2) {
        throw ` ## y data array to short(${y.length}) for given lag of ${lag}`
    }
    //console.log(`lag, threshold, influence: ${lag}, ${threshold}, ${influence}`)

    // init variables
    var signals = Array(y.length).fill(0)
    var filteredY = y.slice(0)
    const lead_in = y.slice(0, lag)
    //console.log("1: " + lead_in.toString())

    var avgFilter = []
    avgFilter[lag - 1] = mean(lead_in)
    var stdFilter = []
    stdFilter[lag - 1] = stddev(lead_in)
    //console.log("2: " + stdFilter.toString())

    for (var i = lag; i < y.length; i++) {
        //console.log(`${y[i]}, ${avgFilter[i-1]}, ${threshold}, ${stdFilter[i-1]}`)
        if (Math.abs(y[i] - avgFilter[i - 1]) > (threshold * stdFilter[i - 1])) {
            if (y[i] > avgFilter[i - 1]) {
                signals[i] = +1 // positive signal
            } else {
                signals[i] = -1 // negative signal
            }
            // make influence lower
            filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i - 1]
        } else {
            signals[i] = 0 // no signal
            filteredY[i] = y[i]
        }

        // adjust the filters
        const y_lag = filteredY.slice(i - lag, i)
        avgFilter[i] = mean(y_lag)
        stdFilter[i] = stddev(y_lag)
    }

    return signals
}

module.exports = smoothed_z_score

到目前为止,我已经将一些其他算法移植到了 javascript。这次来自numercial pyhon,它给了我更多的控制权并且对我来说效果更好。也打包在 npm 中,你可以在华盛顿州立大学的 jupyter 页面上找到更多关于算法的信息。 npmjs.com/package/@joe_six/duarte-watanabe-peak-detection
h
hotpaw2

如果边界值或其他标准取决于未来值,那么唯一的解决方案(没有时间机器或未来值的其他知识)是延迟任何决定,直到一个人有足够的未来值。如果您想要一个高于平均值的水平,例如 20 点,那么您必须等到您在任何峰值决策之前至少有 19 点,否则下一个新点可能会完全超出您 19 点前的阈值.

补充:如果峰高的统计分布可能是重尾分布,而不是均匀分布或高斯分布,那么您可能需要等到看到数千个峰,然后隐藏的帕累托分布不太可能不会产生峰比您以前见过或当前情节中的任何内容大很多倍。除非您事先以某种方式知道下一个点不能是 1e20,否则它可能会出现,在重新调整绘图的 Y 维度后,它会一直保持到该点为止。


就像我之前说的,我们可以假设如果出现峰值,它与图中的峰值一样大,并且明显偏离“正常”值。
如果您提前知道峰值有多大,则将您的平均值和/或阈值预设为略低于该值。
这正是我事先不知道的。
您只是自相矛盾并写道,已知山峰是图片中的大小。你要么知道,要么不知道。
我试图向你解释。你现在明白了吗? '如何识别显着大的峰'。您可以通过统计方法或智能算法来解决问题。对于 .. As large as in the picture,我的意思是:对于存在显着峰值和基本噪声的类似情况。
M
Marc

我认为 delica 的 Python anwser 中有一个错误。我无法对他的帖子发表评论,因为我没有代表这样做,而且编辑队列已满,所以我可能不是第一个注意到它的人。

avgFilter[lag - 1] 和 stdFilter[lag - 1] 在 init 中设置,然后在 lag == i 时再次设置,而不是更改 [lag] 值。这导致第一个信号始终为 1。

这是带有较小更正的代码:

import numpy as np

class real_time_peak_detection():
    def __init__(self, array, lag, threshold, influence):
        self.y = list(array)
        self.length = len(self.y)
        self.lag = lag
        self.threshold = threshold
        self.influence = influence
        self.signals = [0] * len(self.y)
        self.filteredY = np.array(self.y).tolist()
        self.avgFilter = [0] * len(self.y)
        self.stdFilter = [0] * len(self.y)
        self.avgFilter[self.lag - 1] = np.mean(self.y[0:self.lag]).tolist()
        self.stdFilter[self.lag - 1] = np.std(self.y[0:self.lag]).tolist()

    def thresholding_algo(self, new_value):
        self.y.append(new_value)
        i = len(self.y) - 1
        self.length = len(self.y)
        if i < self.lag:
            return 0
        elif i == self.lag:
            self.signals = [0] * len(self.y)
            self.filteredY = np.array(self.y).tolist()
            self.avgFilter = [0] * len(self.y)
            self.stdFilter = [0] * len(self.y)
            self.avgFilter[self.lag] = np.mean(self.y[0:self.lag]).tolist()
            self.stdFilter[self.lag] = np.std(self.y[0:self.lag]).tolist()
            return 0

        self.signals += [0]
        self.filteredY += [0]
        self.avgFilter += [0]
        self.stdFilter += [0]

        if abs(self.y[i] - self.avgFilter[i - 1]) > self.threshold * self.stdFilter[i - 1]:
            if self.y[i] > self.avgFilter[i - 1]:
                self.signals[i] = 1
            else:
                self.signals[i] = -1

            self.filteredY[i] = self.influence * self.y[i] + (1 - self.influence) * self.filteredY[i - 1]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])
        else:
            self.signals[i] = 0
            self.filteredY[i] = self.y[i]
            self.avgFilter[i] = np.mean(self.filteredY[(i - self.lag):i])
            self.stdFilter[i] = np.std(self.filteredY[(i - self.lag):i])

        return self.signals[i]

M
Marc Compere

这种 z-scores 方法在峰值检测方面非常有效,这也有助于去除异常值。异常值对话经常讨论每个点的统计价值和不断变化的数据的伦理。

但在来自容易出错的串行通信或容易出错的传感器的重复、错误传感器值的情况下,错误或虚假读数中没有统计值。他们需要被识别和删除。

从视觉上看,错误是显而易见的。下图中的直线显示了需要删除的内容。但是用算法识别和消除错误是相当具有挑战性的。 Z 分数运行良好。

下图具有通过串行通信从传感器获取的值。偶尔的串行通信错误、传感器错误或两者都会导致重复的、明显错误的数据点。

z-score 峰值检测器能够在虚假数据点上发出信号并生成干净的结果数据集,同时保留正确数据的特征:

https://i.stack.imgur.com/rykCR.jpg


很不错的应用!感谢分享!您是否在将数据输入算法之前对其进行了转换?如果是这样,您究竟使用了什么转换?如果(或何时)公开可用,请随时分享指向您的论文或研究文件的链接;然后,我会将指向您研究的链接添加到我的参考文献列表中。快乐编码! :)
没有转变。顶部的子图是来自数据采集设置的原始数据集。额外的 Matlab 代码大约是 2 行,用于提取没有触发信号的数据集。查找未触及数据点的索引:idx_zero=find(signals==0); 然后使用 y_filtered = y(idx_zero) 提取数据
我花了几个小时从数据采集系统中手动过滤虚假数据点,直到发现这一点,我才找到令人满意的通用算法。在不改变虚假数据点的平均值的情况下过滤新点的单独状态是这里的关键。 Z 分数肯定,但独立过滤器状态很关键
很高兴听你这样说!事实上,信号阈值的单独状态是使该算法非常健壮的关键:) 有趣的是,您甚至不需要转换数据,我预计您需要在应用之前应用一阶差分过滤器算法,但显然这甚至不需要!很酷 :)
这种修补是典型的,但每次都是乏味和习惯的。避免这种情况说明了该算法的价值。该线程中没有太多关于异常值删除的讨论,但这就是我发现它的最佳实用程序的方式。
D
Dharman

ZSCORE 算法的 PHP 实现如下:

<?php
$y = array(1,7,1.1,1,0.9,1,1,1.1,1,0.9,1,1.1,1,1,0.9,1,1,1.1,1,1,1,1,1.1,0.9,1,1.1,1,1,0.9,
       1,1.1,1,1,1.1,1,0.8,0.9,1,1.2,0.9,1,1,1.1,1.2,1,1.5,10,3,2,5,3,2,1,1,1,0.9,1,1,3,
       2.6,4,3,3.2,2,1,1,0.8,4,4,2,2.5,1,1,1);

function mean($data, $start, $len) {
    $avg = 0;
    for ($i = $start; $i < $start+ $len; $i ++)
        $avg += $data[$i];
    return $avg / $len;
}
    
function stddev($data, $start,$len) {
    $mean = mean($data,$start,$len);
    $dev = 0;
    for ($i = $start; $i < $start+$len; $i++) 
        $dev += (($data[$i] - $mean) * ($data[$i] - $mean));
    return sqrt($dev / $len);
}

function zscore($data, $len, $lag= 20, $threshold = 1, $influence = 1) {

    $signals = array();
    $avgFilter = array();
    $stdFilter = array();
    $filteredY = array();
    $avgFilter[$lag - 1] = mean($data, 0, $lag);
    $stdFilter[$lag - 1] = stddev($data, 0, $lag);
    
    for ($i = 0; $i < $len; $i++) {
        $filteredY[$i] = $data[$i];
        $signals[$i] = 0;
    }


    for ($i=$lag; $i < $len; $i++) {
        if (abs($data[$i] - $avgFilter[$i-1]) > $threshold * $stdFilter[$lag - 1]) {
            if ($data[$i] > $avgFilter[$i-1]) {
                $signals[$i] = 1;
            }
            else {
                $signals[$i] = -1;
            }
            $filteredY[$i] = $influence * $data[$i] + (1 - $influence) * $filteredY[$i-1];
        } 
        else {
            $signals[$i] = 0;
            $filteredY[$i] = $data[$i];
        }
        
        $avgFilter[$i] = mean($filteredY, $i - $lag, $lag);
        $stdFilter[$i] = stddev($filteredY, $i - $lag, $lag);
    }
    return $signals;
}

$sig = zscore($y, count($y));

print_r($y); echo "<br><br>";
print_r($sig); echo "<br><br>";

for ($i = 0; $i < count($y); $i++) echo $i. " " . $y[$i]. " ". $sig[$i]."<br>";

一条评论:鉴于此算法将主要用于采样数据,我建议您通过除以 ($len - 1) 而不是 stddev() 中的 $len 来实现 sample standard deviation
S
Sga

@Jean-Paul Smoothed Z Score 算法的 Dart 版本:

class SmoothedZScore {
  int lag = 5;
  num threshold = 10;
  num influence = 0.5;

  num sum(List<num> a) {
    num s = 0;
    for (int i = 0; i < a.length; i++) s += a[i];
    return s;
  }

  num mean(List<num> a) {
    return sum(a) / a.length;
  }

  num stddev(List<num> arr) {
    num arrMean = mean(arr);
    num dev = 0;
    for (int i = 0; i < arr.length; i++) dev += (arr[i] - arrMean) * (arr[i] - arrMean);
    return sqrt(dev / arr.length);
  }

  List<int> smoothedZScore(List<num> y) {
    if (y.length < lag + 2) {
      throw 'y data array too short($y.length) for given lag of $lag';
    }

    // init variables
    List<int> signals = List.filled(y.length, 0);
    List<num> filteredY = List<num>.from(y);
    List<num> leadIn = y.sublist(0, lag);

    var avgFilter = List<num>.filled(y.length, 0);
    var stdFilter = List<num>.filled(y.length, 0);
    avgFilter[lag - 1] = mean(leadIn);
    stdFilter[lag - 1] = stddev(leadIn);

    for (var i = lag; i < y.length; i++) {
      if ((y[i] - avgFilter[i - 1]).abs() > (threshold * stdFilter[i - 1])) {
        signals[i] = y[i] > avgFilter[i - 1] ? 1 : -1;
        // make influence lower
        filteredY[i] = influence * y[i] + (1 - influence) * filteredY[i - 1];
      } else {
        signals[i] = 0; // no signal
        filteredY[i] = y[i];
      }

      // adjust the filters
      List<num> yLag = filteredY.sublist(i - lag, i);
      avgFilter[i] = mean(yLag);
      stdFilter[i] = stddev(yLag);
    }

    return signals;
  }
}

关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅