├── .gitignore ├── README.md ├── README_zh_CN.md ├── docs ├── developer_guide │ ├── Contribution_Guide.md │ └── Contribution_Guide_zh_CN.md └── pic │ └── TestAD_Engin.png ├── pom.xml └── src ├── main └── java │ └── org │ └── algorithmtools │ └── ad4j │ ├── config │ ├── ADMConfigs.java │ └── ConfigOption.java │ ├── engine │ └── AnomalyDetectionEngine.java │ ├── enumtype │ ├── AnomalyDictType.java │ ├── CompareType.java │ ├── LogicType.java │ └── ThresholdType.java │ ├── model │ └── adm │ │ ├── ADM_2ndDerivationMBP.java │ │ ├── ADM_GESD.java │ │ ├── ADM_MannKendall.java │ │ ├── ADM_Quantile.java │ │ ├── ADM_ThresholdRule.java │ │ ├── ADM_ZScore.java │ │ └── AbstractADM.java │ ├── pojo │ ├── AnomalyDetectionContext.java │ ├── AnomalyDetectionLog.java │ ├── AnomalyDetectionProcessCollector.java │ ├── AnomalyDetectionResult.java │ ├── AnomalyIndicatorSeries.java │ ├── IndicatorEvaluateInfo.java │ ├── IndicatorInfo.java │ ├── IndicatorSeries.java │ ├── ThresholdRule.java │ ├── ThresholdRuleBase.java │ └── ThresholdRuleGroup.java │ └── utils │ ├── BandwidthUtil.java │ ├── CollectionUtil.java │ ├── IndicatorCalculateUtil.java │ ├── IndicatorSeriesComparator.java │ └── IndicatorSeriesUtil.java └── test └── java └── org └── algorithmtools ├── ad4j └── model │ ├── ADEngineTest.java │ ├── ADMTest.java │ └── VolatilityAnalysisTest.java ├── chart ├── JFreeChartUtil.java ├── LineChart.java └── ScatterChart.java └── example └── BizUseExample.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | /target/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnomalyDetection-Java 2 | 3 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=socialflat-square&)](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![Total Lines](https://img.shields.io/github/stars/algorithm-tools/ad4j?style=socialflat-square&label=stars)](https://github.com/algorithm-tools/ad4j/stargazers) 5 | [![Release](https://jitpack.io/v/algorithm-tools/ad4j.svg)](https://jitpack.io/#algorithm-tools/ad4j) 6 | [![CN doc](https://img.shields.io/badge/文档-中文版-blue.svg?style=socialflat-square&)](README_zh_CN.md) 7 | [![EN doc](https://img.shields.io/badge/document-English-blue.svg?style=socialflat-square&)](README.md) 8 | 9 | 10 | ## Introduction 11 | > Java implemented anomaly monitoring algorithm library. 12 | 13 | AnomalyDetection Java is an anomaly detection algorithm library based on statistical and machine learning approaches, implemented in Java. It can be easily embedded in actual business to monitor various types of anomalies in data sequences, including outlier anomalies, fluctuation anomalies, trend anomalies, and so on. 14 | 15 | ## Feature 16 | 17 | Its main feature are as follows: 18 | 19 | - Depends on JDK environment and common-math3 library, and conducts graphical testing samples through JFreeChart. 20 | - High scalability: The architecture is very simple. Developers can flexibly and efficiently add business domain anomaly detection rules, or add other types of algorithms. 21 | The following monitoring algorithms are currently supported: 22 | 23 | |Exception type | Algorithm | Principle | Limitation| 24 | |-------------------------|----|----|----| 25 | |Absolute value anomaly | GESD | Calculate generalized extreme student bias statistic to find outliers | Apply normal distribution| 26 | |Absolute value anomaly | Z-score | Z-Score based detection | Apply normal distribution| 27 | |Absolute value anomaly | Quantile | Quantile based statistical algorithm | Applicable to all distributions| 28 | |Abnormal fluctuations | 2nd derivative+distance | Finding MBP based on second derivative and distance | Normal and non normal distributions of inflection points| 29 | |Trend anomaly | Mann Kendall | Based on Mann Kendall test | Applicable to all distributions| 30 | |Threshold anomaly | Threshold rule engine | Based on business threshold rules | Applicable to all distributions| 31 | 32 | # Demos 33 | - [ad4j-Demos](https://github.com/algorithm-tools/ad4j/tree/main/src/test/java/org/algorithmtools/example) 34 | 35 | # For Developers 36 | ## Building ad4j 37 | you can build ad4j using Maven by issuing the following command from the root directory of the project: 38 | ```text 39 | mvn clean install -Dmaven.test.skip=true 40 | ``` 41 | The build requires JDK 8 or later. 42 | 43 | ## Using ad4j 44 | - You can bring in the source code or by maven. Add to maven pom: 45 | ```xml 46 | 47 | 48 | jitpack.io 49 | https://jitpack.io 50 | 51 | 52 | 53 | 54 | 55 | com.github.algorithm-tools 56 | ad4j 57 | 1.0.0 58 | 59 | 60 | ``` 61 | - business use: 62 | `BizUseExample` 63 | ```java 64 | public class BizUseExample { 65 | 66 | public static void main(String[] args) { 67 | indicatorDetect(); 68 | } 69 | 70 | public static void indicatorDetect(){ 71 | // 1. Transfer biz data to indicator series info 72 | long currentTime = System.currentTimeMillis(); 73 | List indicatorSeries = new ArrayList<>(); 74 | indicatorSeries.add(new IndicatorSeries(currentTime + 1, 1d, "logicalIndex-1")); 75 | indicatorSeries.add(new IndicatorSeries(currentTime + 2, 2d, "logicalIndex-2")); 76 | indicatorSeries.add(new IndicatorSeries(currentTime + 3, 3d, "logicalIndex-3")); 77 | indicatorSeries.add(new IndicatorSeries(currentTime + 4, 4d, "logicalIndex-4")); 78 | indicatorSeries.add(new IndicatorSeries(currentTime + 5, 40d, "logicalIndex-5")); 79 | indicatorSeries.add(new IndicatorSeries(currentTime + 6, 6d, "logicalIndex-6")); 80 | indicatorSeries.add(new IndicatorSeries(currentTime + 7, 7d, "logicalIndex-7")); 81 | indicatorSeries.add(new IndicatorSeries(currentTime + 8, 8d, "logicalIndex-8")); 82 | indicatorSeries.add(new IndicatorSeries(currentTime + 9, 9d, "logicalIndex-9")); 83 | indicatorSeries.add(new IndicatorSeries(currentTime + 10, 10d, "logicalIndex-10")); 84 | 85 | IndicatorInfo info = new IndicatorInfo("Example", "Example-Name", indicatorSeries); 86 | 87 | // 2. New AnomalyDetectionEngine to detect 88 | AnomalyDetectionEngine engine = new AnomalyDetectionEngine(); 89 | AnomalyDetectionResult detectionResult = engine.detect(info); 90 | 91 | // 3. Business process detect result. Like Records,Alarms,Print 92 | IndicatorSeriesUtil.print(detectionResult); 93 | } 94 | 95 | } 96 | ``` 97 | 98 | # Test case 99 | > ADEngineTest.java 100 | 101 | - Indicator original data:`10.0, 12.0, 12.5, 133.0, 13.0, 10.5, 100.0, 14.0, 15.0, 14.5, 15.5` 102 | - This is Line chart: 103 | ![TestAD_Engin_LineChart](docs/pic/TestAD_Engin.png "TestAD_Engin_LineChart") 104 | - `ADEngineTest`print result: 105 | ```text 106 | ==============Anomaly Detection Result============= 107 | 1.Anomaly detection original data:[[0, 10.0, 0], [1, 12.0, 1], [2, 12.5, 2], [3, 133.0, 3], [4, 13.0, 4], [5, 10.5, 5], [6, 100.0, 6], [7, 14.0, 7], [8, 15.0, 8], [9, 14.5, 9], [10, 15.5, 10]] 108 | 2.Overview: has 3 types anomaly detected. 109 | 3.1.[Anomaly: Fluctuation Anomaly] [{"anomalyDetectionModel":"MODEL_ADM_2ndDerivationMBP","anomalyType":"TYPE_FLUCTUATION","hasAnomaly":true,"normalRangeMax":19.75,"normalRangeMin":7.75,"seriesList":[{"logicalIndex":"3","time":3,"value":133.0}]}] 110 | 3.2.[Anomaly: Outliers Anomaly] [{"anomalyDetectionModel":"MODEL_ADM_GESD","anomalyType":"TYPE_OUTLIERS_VALUE","hasAnomaly":true,"normalRangeMax":0.0,"normalRangeMin":0.0,"seriesList":[{"logicalIndex":"3","time":3,"value":133.0},{"logicalIndex":"6","time":6,"value":100.0}]},{"anomalyDetectionModel":"MODEL_ADM_Quantile","anomalyType":"TYPE_OUTLIERS_VALUE","hasAnomaly":true,"normalRangeMax":19.75,"normalRangeMin":7.75,"seriesList":[{"$ref":"$[0].seriesList[0]"},{"$ref":"$[0].seriesList[1]"}]}] 111 | 3.3.[Anomaly: Trend Anomaly] [{"anomalyDetectionModel":"MODEL_ADM_ManKendall","anomalyTrend":"TREND_UP","anomalyType":"TYPE_TREND","hasAnomaly":true,"normalRangeMax":0.0,"normalRangeMin":0.0}] 112 | ==============Anomaly Detection Result End========= 113 | click Enter to close window... 114 | ``` 115 | More monitoring algorithms are being added, welcome to co build. 116 | 117 | # Participate in Contributions 118 | 119 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/algorithm-tools/ad4j/pulls) 120 | 121 | Welcome to join the community, build a win-win situation, please refer to the contribution process: [How to contribute](https://github.com/algorithm-tools/ad4j/blob/main/docs/developer_guide/Contribution_Guide.md). 122 | 123 | Thank you to all the people who already contributed to AnomalyDetection-Java! 124 | 125 | [![Contributors](https://contrib.rocks/image?repo=algorithm-tools/AnomalyDetection-Java)](https://github.com/algorithm-tools/ad4j/graphs/contributors) 126 | 127 | 128 | # Get Help 129 | 130 | - Create an issue and describe it clearly. 131 | -------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # AnomalyDetection-Java 2 | 3 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg?style=socialflat-square&)](https://www.apache.org/licenses/LICENSE-2.0.html) 4 | [![Total Lines](https://img.shields.io/github/stars/algorithm-tools/ad4j?style=socialflat-square&label=stars)](https://github.com/algorithm-tools/ad4j/stargazers) 5 | [![Release](https://jitpack.io/v/algorithm-tools/ad4j.svg)](https://jitpack.io/#algorithm-tools/ad4j) 6 | [![CN doc](https://img.shields.io/badge/文档-中文版-blue.svg?style=socialflat-square&)](README_zh_CN.md) 7 | [![EN doc](https://img.shields.io/badge/document-English-blue.svg?style=socialflat-square&)](README.md) 8 | 9 | 10 | ## 简介 11 | > Java 实现的异常监测算法库。 12 | 13 | AnomalyDetection-Java是一个基于统计学、机器学习等思路实现的异常检测算法库,使用java实现。可以很方便的嵌入实际业务中,对数据序列进行多种异常类型的监测包括:离群值异常、波动异常、趋势异常等类型。 14 | 15 | ## 特性 16 | 17 | 主要包含如下特性: 18 | 19 | - 依赖JDK环境和common-math3库,并通过JFreeChart进行图形化测试样例。 20 | - 高扩展性:架构十分简单。开发者可以灵活高效的增加业务域异常检测规则,或者增加其他类型的算法。 21 | 当前支持如下监测算法: 22 | 23 | | 异常类型 | 算法 | 原理 | 限制 | 24 | |--------------|---------------------|----------------|----| 25 | | 绝对值异常 | GESD | 计算广义极端学生化偏差统计量寻找异常点 | 适用正态分布 | 26 | | 绝对值异常 | Z-score | 基于Z-score算法 | 适用正态分布、异常点较少情况 | 27 | | 绝对值异常 | Quantile | 基于分位统计算法 | 适用所有分布 | 28 | | 波动异常 | 2阶导+距离 | 基于二阶导数和距离寻找MBP | 拐点存在的正态、非正态分布 | 29 | | 趋势异常 | Mann-Kendall | 基于MannKendall检验 | 适用所有分布 | 30 | | 阈值异常 | 阈值规则引擎 | 基于业务阈值规则 | 适用所有分布 | 31 | 32 | # Demos 33 | - [ad4j-Demos](https://github.com/algorithm-tools/ad4j/tree/main/src/test/java/org/algorithmtools/example) 34 | 35 | # For Developers 36 | ## Building ad4j 37 | you can build ad4j using Maven by issuing the following command from the root directory of the project: 38 | ```text 39 | mvn clean install -Dmaven.test.skip=true 40 | ``` 41 | The build requires JDK 8 or later. 42 | 43 | ## Using ad4j 44 | - You can bring in the source code or by maven. Add to maven pom: 45 | ```xml 46 | 47 | 48 | jitpack.io 49 | https://jitpack.io 50 | 51 | 52 | 53 | 54 | 55 | com.github.algorithm-tools 56 | ad4j 57 | 1.0.0 58 | 59 | 60 | ``` 61 | - business use: 62 | `BizUseExample` 63 | ```java 64 | public class BizUseExample { 65 | 66 | public static void main(String[] args) { 67 | indicatorDetect(); 68 | } 69 | 70 | public static void indicatorDetect(){ 71 | // 1. Transfer biz data to indicator series info 72 | long currentTime = System.currentTimeMillis(); 73 | List indicatorSeries = new ArrayList<>(); 74 | indicatorSeries.add(new IndicatorSeries(currentTime + 1, 1d, "logicalIndex-1")); 75 | indicatorSeries.add(new IndicatorSeries(currentTime + 2, 2d, "logicalIndex-2")); 76 | indicatorSeries.add(new IndicatorSeries(currentTime + 3, 3d, "logicalIndex-3")); 77 | indicatorSeries.add(new IndicatorSeries(currentTime + 4, 4d, "logicalIndex-4")); 78 | indicatorSeries.add(new IndicatorSeries(currentTime + 5, 40d, "logicalIndex-5")); 79 | indicatorSeries.add(new IndicatorSeries(currentTime + 6, 6d, "logicalIndex-6")); 80 | indicatorSeries.add(new IndicatorSeries(currentTime + 7, 7d, "logicalIndex-7")); 81 | indicatorSeries.add(new IndicatorSeries(currentTime + 8, 8d, "logicalIndex-8")); 82 | indicatorSeries.add(new IndicatorSeries(currentTime + 9, 9d, "logicalIndex-9")); 83 | indicatorSeries.add(new IndicatorSeries(currentTime + 10, 10d, "logicalIndex-10")); 84 | 85 | IndicatorInfo info = new IndicatorInfo("Example", "Example-Name", indicatorSeries); 86 | 87 | // 2. New AnomalyDetectionEngine to detect 88 | AnomalyDetectionEngine engine = new AnomalyDetectionEngine(); 89 | AnomalyDetectionResult detectionResult = engine.detect(info); 90 | 91 | // 3. Business process detect result. Like Records,Alarms,Print 92 | IndicatorSeriesUtil.print(detectionResult); 93 | } 94 | 95 | } 96 | ``` 97 | 98 | 99 | # Test case 100 | > ADEngineTest.java 101 | 102 | - Indicator original data:`10.0, 12.0, 12.5, 133.0, 13.0, 10.5, 100.0, 14.0, 15.0, 14.5, 15.5` 103 | - This is Line chart: 104 | ![TestAD_Engin_LineChart](docs/pic/TestAD_Engin.png "TestAD_Engin_LineChart") 105 | - `ADEngineTest`print result: 106 | ```text 107 | ==============Anomaly Detection Result============= 108 | 1.Anomaly detection original data:[[0, 10.0, 0], [1, 12.0, 1], [2, 12.5, 2], [3, 133.0, 3], [4, 13.0, 4], [5, 10.5, 5], [6, 100.0, 6], [7, 14.0, 7], [8, 15.0, 8], [9, 14.5, 9], [10, 15.5, 10]] 109 | 2.Overview: has 3 types anomaly detected. 110 | 3.1.[Anomaly: Fluctuation Anomaly] [{"anomalyDetectionModel":"MODEL_ADM_2ndDerivationMBP","anomalyType":"TYPE_FLUCTUATION","hasAnomaly":true,"normalRangeMax":19.75,"normalRangeMin":7.75,"seriesList":[{"logicalIndex":"3","time":3,"value":133.0}]}] 111 | 3.2.[Anomaly: Outliers Anomaly] [{"anomalyDetectionModel":"MODEL_ADM_GESD","anomalyType":"TYPE_OUTLIERS_VALUE","hasAnomaly":true,"normalRangeMax":0.0,"normalRangeMin":0.0,"seriesList":[{"logicalIndex":"3","time":3,"value":133.0},{"logicalIndex":"6","time":6,"value":100.0}]},{"anomalyDetectionModel":"MODEL_ADM_Quantile","anomalyType":"TYPE_OUTLIERS_VALUE","hasAnomaly":true,"normalRangeMax":19.75,"normalRangeMin":7.75,"seriesList":[{"$ref":"$[0].seriesList[0]"},{"$ref":"$[0].seriesList[1]"}]}] 112 | 3.3.[Anomaly: Trend Anomaly] [{"anomalyDetectionModel":"MODEL_ADM_ManKendall","anomalyTrend":"TREND_UP","anomalyType":"TYPE_TREND","hasAnomaly":true,"normalRangeMax":0.0,"normalRangeMin":0.0}] 113 | ==============Anomaly Detection Result End========= 114 | click Enter to close window... 115 | ``` 116 | 117 | 更多监测算法加入中,欢迎加入共建! 118 | 119 | # 参与贡献 120 | 121 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](https://github.com/algorithm-tools/ad4j/pulls) 122 | 123 | 欢迎加入共建共赢, 贡献流程请参考:[参与贡献](https://github.com/algorithm-tools/ad4j/blob/main/docs/developer_guide/Contribution_Guide_zh_CN.md). 124 | 125 | 感谢所有做出贡献的人! 126 | 127 | [![Contributors](https://contrib.rocks/image?repo=algorithm-tools/AnomalyDetection-Java)](https://github.com/algorithm-tools/ad4j/graphs/contributors) 128 | 129 | 130 | # 获得帮助 131 | 132 | - 创建 issue,并描述清晰 -------------------------------------------------------------------------------- /docs/developer_guide/Contribution_Guide.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | - Submit an issue on GitHub to receive answers to questions 3 | - Answer other people's issue questions 4 | - Discuss the implementation of new features 5 | - Submit bug fixes or feature PRs 6 | - Publish technical articles related to application case practice 7 | 8 | 9 | - **General process of contribution**: 10 | - 1. fork to your own repository 11 | - 2. git clone to your own repository to local 12 | - 3. new branch: need to develop what Feature or bug processing, on a related issue title to create a new branch, for example (from the main branch to create a branch to fix the bug, issue number is 3): main/bug-3 13 | - 4. Submit the code: After development, self-test, and submit the code push to the remote end of their own warehouse, submit the code instructions see below. 14 | - 5. Enter your own warehouse page, submit PR. 15 | - 6. Community Committer will do CodeReview, may discuss some details with you, no problem will be merged into the appropriate branch after your submission. 16 | - 7. When your PR is merged, congratulations, become an official contributor of the current project, your information will be displayed on the project home page Contributors position after a period of time. 17 | 18 | 19 | 20 | # Automatically synchronize the master repository 21 | > Automatically synchronize the code from the main repository (upstream) to your repository (origin) to keep your repository code synchronized with the main repository code. 22 | 23 | **Steps** 24 | - Fork the main repository to your GitHub account. 25 | - Click on the repository you want to fork, and click on the Actions tab. 26 | - You will be prompted for some information, click the `I understand my workflows, go ahead and enable them` button. 27 | - Once this is done, the synchronization will be performed at 00:00:00 every day. If the workflow file is not changed, the synchronization will continue to be performed automatically according to the timer configuration. 28 | 29 | 30 | # Unsolicited Pickup of Issue or Feature 31 | If you want to implement a Feature or fix a bug. please refer to the following: 32 | - First fork ad4j to your personal repository. 33 | - All discovered bugs and new Features are recommended to be managed using the Issues Page. 34 | - If you want to develop and implement a Feature, please reply to the Issue associated with the Feature, indicating that you are currently working on it. If you want to develop a Feature feature, reply to the Issue it is associated with to show that you are currently working on the Issue, and when you do, set a deadline for yourself and add it to the reply. 35 | - You should start your work by creating a new branch, the name of which you should refer to the Pull Request. For example, if you want to complete the feature and submit an Issue demo, your branch name should be feature-demo. 36 | - When you're done, send a Pull Request to ad4j's main branch. See below for submission PRs. 37 | 38 | 39 | 40 | # Submit Issue 41 | 42 | Title format: [Issue type][`module name`] Issue Description 43 | Where Issue type is as follows: 44 | 45 | 46 | 47 | | Issue type | Desc | Case | 48 | |------------|---------------------------------------------------------------|--------------------------------------------------------------------------| 49 | | Feature | Includes desired new features and functionality | [Feature][adm] Add xxx | 50 | | Bug | Bugs in the program | [Bug][adm] gesd exception when some env | 51 | | Improve | Improvements, not limited to code format, program performance | [Improve][adm] 2ndDerivationMBP Filter without significant fluctuations. | 52 | | Test | Specific to the test case section | [Test][adm] Add 2ndDerivationMBP test | 53 | | Doc | doc desc | [Doc][doc] update xxx. | 54 | 55 | - Module name can be filled with the name of the package mainly affected, or * if it mainly affects most packages or more packages. 56 | - If it is a Bug Issue, then you need to describe the bug in detail: the problem encountered, the steps to reproduce it (generously post screenshots), the version, your considerations and suggestions. 57 | - If it is Feature class Issue, you need to describe the function in detail: detailed description of the function, usage, scope of impact, implementation plan and other key information. 58 | 59 | 60 | # Submit code 61 | Submission Instructions: [Submission Type] [Impact Module] Content 62 | 63 | See the [Submit Issue] section for types and impact modules. 64 | > Suggested submission type spliced with Issue number e.g.: feature-#2 65 | 66 | # Submit PR 67 | - Submission Title Format: [Submission Type - Issue Number] [Impact Module] Topic 68 | 69 | See the [Submission Issue] section for types and impact modules. The Impact Module is not required. 70 | 71 | - Submission Content Format: 72 | - Itemized statement of changes 73 | - A single line associated with specific Issues (can be more than one), e.g. for Issue #16: Issue #16 74 | - Statements do not need to end with a period 75 | 76 | - Case: 77 | ```text 78 | [Doc-001][doc] add commit message 79 | 80 | - commit message RIP 81 | - build some conventions 82 | - help the commit messages become clean and tidy 83 | - help developers and release managers better track issues and clarify the optimization in the version iteration 84 | 85 | - Issues: Issues #001 86 | ``` 87 | -------------------------------------------------------------------------------- /docs/developer_guide/Contribution_Guide_zh_CN.md: -------------------------------------------------------------------------------- 1 | # 如何贡献 2 | - 通过github提交issue获得问题解答 3 | - 回答别人的issue问题 4 | - 讨论新Feature的实现 5 | - 提交bugfix或者feature的PR 6 | - 发表应用案例实践相关技术文章 7 | 8 | 9 | - **贡献的一般流程**: 10 | - 1.fork 到自己仓库 11 | - 2.把自己仓库git clone到本地 12 | - 3.新建分支:需要开发什么Feature或bug处理,就以相关issue标题创建新分支,例如(从main分支上创建一个修复bug的分支,issue编号是3):main/bug-3 13 | - 4.提交代码:开发完后,进行自测,并提交代码push到自己仓库远端,提交代码说明见下文。 14 | - 5.进入自己仓库页面,提交PR。 15 | - 6.社区Committer们会做CodeReview,可能会跟你讨论一些细节,没什么问题后会将你的提交合并到相应分支。 16 | - 7.当你的PR被Merge后,恭喜你,成为当前项目的一名官方Contributor,你的信息会在一段时间后显示在项目主页Contributors位置。 17 | 18 | 19 | # 自动同步主仓库 20 | > 自动同步主仓库 (upstream) 的代码到你的仓库 (origin),保持你的仓库代码与主仓库代码同步。 21 | 22 | **步骤** 23 | - Fork 主仓库到你的 GitHub 账号下. 24 | - 点击 Fork 之后的仓库, 点击 Actions 标签. 25 | - 会提示一些介绍, 点击 `I understand my workflows, go ahead and enable them` 按钮. 26 | - 完成之后,会在每天的 00:00:00 执行同步操作. 后续该 workflow 文件不改变的情况下,会一直按照定时配置自动执行同步操作. 27 | 28 | # 主动领取Issue或Feature 29 | 如果你想实现某个 Feature 或者修复某个 Bug。请参考以下内容: 30 | - 首先fork ad4j到你的个人仓库。 31 | - 所有发现的Bug 与新 Feature 建议使用 Issues Page 进行管理。 32 | - 如果想要开发实现某个 Feature 功能,请先回复该功能所关联的 Issue,表明你当前正在这个 Issue 上工作。 并在回复的时候为自己设置一个最后期限,并添加到回复内容中。 33 | - 你应该新建一个分支来开始你的工作,分支的名字参考 Pull Request 需知。比如,你想完成 feature 功能并提交了 Issue demo,那么你的 branch 名字应为 feature-demo。 34 | - 完成后,发送一个 Pull Request 到 ad4j 的 main 分支。提交PR参考下方内容。 35 | 36 | 37 | # 提交Issue 38 | 39 | 标题格式:[Issue 类型][`模块名`] Issue 描述 40 | 其中Issue 类型如下: 41 | 42 | 43 | 44 | | Issue type | Desc | Case | 45 | |------------|-----------------|--------------------------------------------------------------------------| 46 | | Feature | 包含所期望的新功能和新特性 | [Feature][adm] Add xxx | 47 | | Bug | 程序中存在的Bug | [Bug][adm] gesd exception when some env | 48 | | Improve | 改进,不限于代码格式,程序性能 | [Improve][adm] 2ndDerivationMBP Filter without significant fluctuations. | 49 | | Test | 专门针对测试用例部分 | [Test][adm] Add 2ndDerivationMBP test | 50 | | Doc | 文档部分 | [Doc][doc] update xxx. | 51 | 52 | - 模块名可以填主要影响的包名,如果主要影响大部分包或较多包,可以填 * 53 | - 如果是Bug Issue,那么需要描述bug详细:遇到的问题、复现步骤(大方的贴上截图)、版本、你的考虑和建议。 54 | - 如果是Feature类的Issue,需要详细描述功能:功能详细描述、用途、影响范围、实现方案等关键信息。 55 | 56 | 57 | # 提交代码 58 | 提交说明:[提交类型][影响模块] 内容 59 | 60 | 类型和影响模块参见【提交Issue】部分。 61 | > 建议提交类型拼接Issue编号例如:feature-#2 62 | 63 | # 提交PR 64 | - 提交标题格式:[提交类型-Issue编号][影响模块] 主题 65 | 66 | 类型和影响模块参见【提交Issue】部分。影响模块非必须。 67 | 68 | - 提交内容格式: 69 | - 逐项陈述改动的内容 70 | - 单独一行关联具体Issues(可以多个),例如关联编号为#16的Issue: Issue #16 71 | - 语句不需要句号结尾 72 | 73 | - 举个例子: 74 | ```text 75 | [Doc-001][doc] add commit message 76 | 77 | - commit message RIP 78 | - build some conventions 79 | - help the commit messages become clean and tidy 80 | - help developers and release managers better track issues and clarify the optimization in the version iteration 81 | 82 | - Issues: Issues #001 83 | ``` 84 | -------------------------------------------------------------------------------- /docs/pic/TestAD_Engin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorithm-tools/ad4j/79f1e2cb0ae033676de3435aa8ce71947f515cb4/docs/pic/TestAD_Engin.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | jar 8 | 9 | ad4j 10 | org.algorithmtools 11 | 0.1 12 | 13 | ad4j: Anomaly Detection For Java 14 | Java implemented anomaly detection library. 15 | 16 | 17 | 8 18 | 8 19 | UTF-8 20 | 21 | 22 | 23 | 24 | junit 25 | junit 26 | 4.9 27 | test 28 | 29 | 30 | 31 | org.jfree 32 | jfreechart 33 | 1.5.3 34 | provided 35 | 36 | 37 | 38 | org.apache.commons 39 | commons-math3 40 | 3.6.1 41 | 42 | 43 | 44 | org.apache.commons 45 | commons-lang3 46 | 3.12.0 47 | 48 | 49 | 50 | com.alibaba 51 | fastjson 52 | 1.2.76 53 | 54 | 55 | 56 | org.slf4j 57 | slf4j-api 58 | 1.7.36 59 | 60 | 61 | 62 | log4j 63 | log4j 64 | 1.2.17 65 | 66 | 67 | 68 | com.github.haifengl 69 | smile-math 70 | 2.6.0 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | org.apache.maven.plugins 80 | maven-shade-plugin 81 | 3.5.0 82 | 83 | 84 | 85 | shade 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/config/ADMConfigs.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.config; 2 | 3 | import org.algorithmtools.ad4j.pojo.ThresholdRuleBase; 4 | import org.apache.commons.lang3.reflect.FieldUtils; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class ADMConfigs { 11 | 12 | public static final Map configKeyMap = new HashMap<>(); 13 | 14 | //==================ADM Quantile Options=================// 15 | public static final ConfigOption ADM_QUANTILE_USE = new ConfigOption<>("adm.quantile.use", true, "Open Quantile detection function."); 16 | public static final ConfigOption ADM_QUANTILE_IQR_MULTIPLIER = new ConfigOption<>("adm.quantile.iqr.multiplier", 1.5, "Quantile detection IQR multiplier. Support config with sensitivity 0.1s-> low sensitivity; 0.05s-> normal sensitivity; 0.01s-> high sensitivity."); 17 | 18 | //==================ADM Z-score Options=================// 19 | public static final ConfigOption ADM_ZSCORE_USE = new ConfigOption<>("adm.zscore.use", true, "Open Z-score detection function."); 20 | public static final ConfigOption ADM_ZSCORE_THRESHOLD = new ConfigOption<>("adm.zscore.threshold", 2.0, "Z-score detection threshold. Support config with sensitivity 0.1s-> low sensitivity; 0.05s-> normal sensitivity; 0.01s-> high sensitivity."); 21 | 22 | //==================ADM GESD Options=================// 23 | public static final ConfigOption ADM_GESD_USE = new ConfigOption<>("adm.gesd.use", true, "Open GESD detection function."); 24 | public static final ConfigOption ADM_GESD_ALPHA = new ConfigOption<>("adm.gesd.alpha", 0.05, "GESD detection alpha. Indicate abnormal significance. Support config with sensitivity 0.1s-> low sensitivity; 0.05s-> normal sensitivity; 0.01s-> high sensitivity."); 25 | 26 | //==================ADM 2ndDerivationMBP Options=================// 27 | public static final ConfigOption ADM_2ED_DERIVATION_MBP_USE = new ConfigOption<>("adm.2nd_derivation_mbp.use", true, "Open 2ndDerivationMBP detection function."); 28 | public static final ConfigOption ADM_2ED_DERIVATION_MBP_THRESHOLD_FACTOR = new ConfigOption<>("adm.2nd_derivation_mbp.threshold_factor", 1.0, "2nd derivation threshold factor, Regulates the robustness of the evaluation algorithm, the smaller it is, the less robust it is and the more sensitive it is. Support config with sensitivity 0.1s-> low sensitivity; 0.05s-> normal sensitivity; 0.01s-> high sensitivity."); 29 | public static final ConfigOption ADM_2ED_DERIVATION_MBP_EVALUATE_TYPE = new ConfigOption<>("adm.2nd_derivation_mbp.evaluate_type", 1, "2nd derivation evaluate type, Evaluate algorithm types:" + 30 | "1. Distribution by volatility. That is, looking for anomalies based on the volatility distribution of the data" + 31 | ";2. By absolute volatility. That is, the point at which volatility outliers are found is the volatility point.1 finer and more sensitive, 2. more robust. Both can be adjusted with adm.2nd_derivation_mbp.threshold_factor"); 32 | 33 | //==================ADM MannKendall Options=================// 34 | public static final ConfigOption ADM_MANNKENDALL_USE = new ConfigOption<>("adm.mannkendall.use", true, "Open 2ndDerivationMBP detection function."); 35 | /* 计算Z-critical值的代码如下 36 | from scipy.stats import norm 37 | # 设置显著性水平(默认α=0.05)0.05->1.9600;0.01-> 2.5758;0.1->1.6449 38 | alpha = 0.01 39 | # 计算标准正态分布的双侧临界值 40 | z_critical = norm.ppf(1 - alpha/2) 41 | print(f"显著性水平α={alpha}时,双侧检验的Z临界值为:{z_critical:.4f}") 42 | * */ 43 | public static final ConfigOption ADM_MANNKENDALL_CRITICALZ = new ConfigOption<>("adm.mannkendall.criticalz", 1.96, "The Z-critical value corresponding to significance level P=0.05 (refer to the standard normal distribution table). Support config with sensitivity 0.1s-> low sensitivity; 0.05s-> normal sensitivity; 0.01s-> high sensitivity."); 44 | 45 | //==================ADM Threshold-Rule Options=================// 46 | public static final ConfigOption ADM_THRESHOLD_RULE_USE = new ConfigOption<>("adm.threshold_rule.use", false, "Open threshold rule detection function."); 47 | public static final ConfigOption ADM_THRESHOLD_RULE_SET = new ConfigOption<>("adm.threshold_rule.set", null, "Threshold rule detection sets."); 48 | 49 | 50 | static { 51 | ADMConfigs admConfigs = new ADMConfigs(); 52 | Arrays.stream(FieldUtils.getAllFields(ADMConfigs.class)).filter((f) -> { 53 | return ConfigOption.class.isAssignableFrom(f.getType()); 54 | }).forEach((f) -> { 55 | try { 56 | ConfigOption co = (ConfigOption) f.get(admConfigs); 57 | configKeyMap.put(co.getKey(), co); 58 | } catch (IllegalAccessException var4) { 59 | } 60 | }); 61 | } 62 | 63 | public static void main(String[] args) { 64 | System.out.println(configKeyMap); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/config/ConfigOption.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.config; 2 | 3 | 4 | import java.util.Objects; 5 | 6 | public class ConfigOption { 7 | 8 | private final String key; 9 | 10 | private final T defaultValue; 11 | 12 | private final String description; 13 | 14 | public ConfigOption(String key, T defaultValue, String description) { 15 | this.key = key; 16 | this.defaultValue = defaultValue; 17 | this.description = description; 18 | } 19 | 20 | public String getKey() { 21 | return key; 22 | } 23 | 24 | public T getDefaultValue() { 25 | return defaultValue; 26 | } 27 | 28 | public String getDescription() { 29 | return description; 30 | } 31 | 32 | @Override 33 | public boolean equals(Object o) { 34 | if (this == o) return true; 35 | if (o == null || getClass() != o.getClass()) return false; 36 | ConfigOption that = (ConfigOption) o; 37 | return Objects.equals(key, that.key) && Objects.equals(defaultValue, that.defaultValue) && Objects.equals(description, that.description); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return Objects.hash(key, defaultValue, description); 43 | } 44 | 45 | @Override 46 | public String toString() { 47 | return "ConfigOption{" + 48 | "key='" + key + '\'' + 49 | ", defaultValue=" + defaultValue + 50 | ", description='" + description + '\'' + 51 | '}'; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/engine/AnomalyDetectionEngine.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.engine; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.model.adm.*; 6 | import org.algorithmtools.ad4j.pojo.*; 7 | import org.algorithmtools.ad4j.utils.CollectionUtil; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.LinkedHashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class AnomalyDetectionEngine { 16 | 17 | private static final Logger LOGGER = LoggerFactory.getLogger(AnomalyDetectionEngine.class); 18 | 19 | private final LinkedHashMap admMap = new LinkedHashMap<>(); 20 | 21 | private AnomalyDetectionContext context; 22 | 23 | public AnomalyDetectionEngine() { 24 | this(null); 25 | } 26 | 27 | public AnomalyDetectionEngine(AnomalyDetectionContext context) { 28 | this.context = context == null ? AnomalyDetectionContext.createDefault() : context; 29 | 30 | AbstractADM adm = null; 31 | if((Boolean) this.context.getConfig(ADMConfigs.ADM_QUANTILE_USE)){ 32 | adm = new ADM_Quantile(); 33 | admMap.put(adm.getAnomalyDetectionModel(), adm); 34 | this.context.addConfigAnomalyDetectionModel(AnomalyDictType.MODEL_ADM_Quantile); 35 | } 36 | if((Boolean) this.context.getConfig(ADMConfigs.ADM_ZSCORE_USE)){ 37 | adm = new ADM_ZScore(); 38 | admMap.put(adm.getAnomalyDetectionModel(), adm); 39 | this.context.addConfigAnomalyDetectionModel(AnomalyDictType.MODEL_ADM_ZScore); 40 | } 41 | if((Boolean) this.context.getConfig(ADMConfigs.ADM_GESD_USE)){ 42 | adm = new ADM_GESD(); 43 | admMap.put(adm.getAnomalyDetectionModel(), adm); 44 | this.context.addConfigAnomalyDetectionModel(AnomalyDictType.MODEL_ADM_GESD); 45 | } 46 | if((Boolean) this.context.getConfig(ADMConfigs.ADM_2ED_DERIVATION_MBP_USE)){ 47 | adm = new ADM_2ndDerivationMBP(); 48 | admMap.put(adm.getAnomalyDetectionModel(), adm); 49 | this.context.addConfigAnomalyDetectionModel(AnomalyDictType.MODEL_ADM_2ndDerivationMBP); 50 | } 51 | if((Boolean) this.context.getConfig(ADMConfigs.ADM_MANNKENDALL_USE)){ 52 | adm = new ADM_MannKendall(); 53 | admMap.put(adm.getAnomalyDetectionModel(), adm); 54 | this.context.addConfigAnomalyDetectionModel(AnomalyDictType.MODEL_ADM_ManKendall); 55 | } 56 | if((Boolean) this.context.getConfig(ADMConfigs.ADM_THRESHOLD_RULE_USE)){ 57 | adm = new ADM_ThresholdRule(); 58 | admMap.put(adm.getAnomalyDetectionModel(), adm); 59 | this.context.addConfigAnomalyDetectionModel(AnomalyDictType.MODEL_ADM_ThresholdRule); 60 | } 61 | 62 | initADM(this.context); 63 | } 64 | 65 | public void initADM(AnomalyDetectionContext context) { 66 | admMap.values().forEach(v -> v.init(context)); 67 | } 68 | 69 | public AnomalyDetectionResult detect(IndicatorInfo indicatorInfo) { 70 | if (CollectionUtil.isEmpty(indicatorInfo.getIndicatorSeries())) { 71 | return null; 72 | } 73 | 74 | AnomalyDetectionResult detectionResult = new AnomalyDetectionResult(indicatorInfo); 75 | for (Map.Entry admEntry : admMap.entrySet()) { 76 | LOGGER.info("anomaly detect, [{}] processing.", admEntry.getKey()); 77 | IndicatorEvaluateInfo evaluateResult = evaluate(admEntry.getValue(), indicatorInfo.getIndicatorSeries()); 78 | if(evaluateResult.isHasAnomaly()){ 79 | detectionResult.addIndicatorEvaluateInfo(evaluateResult.getAnomalyType(), evaluateResult); 80 | } 81 | LOGGER.info("anomaly detect, [{}] process over.", admEntry.getKey()); 82 | } 83 | 84 | return detectionResult; 85 | } 86 | 87 | private IndicatorEvaluateInfo evaluate(AbstractADM adm, List seriesList) { 88 | AnomalyDetectionLog log = new AnomalyDetectionLog(); 89 | if (!adm.checkCompatibility(seriesList, log)) { 90 | log.info("anomaly detect,[{}] indicator not compatibility."); 91 | return null; 92 | } 93 | 94 | IndicatorEvaluateInfo evaluate = adm.evaluate(seriesList, log); 95 | log.print(); 96 | 97 | if (LOGGER.isDebugEnabled()) { 98 | LOGGER.debug("anomaly detect, [{}] evaluate result:{}", adm.getAnomalyDetectionModel(), evaluate); 99 | } 100 | 101 | return evaluate; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/enumtype/AnomalyDictType.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.enumtype; 2 | 3 | public enum AnomalyDictType { 4 | 5 | //=================MODEL======================// 6 | 7 | /** Quantile-based absolute value monitoring*/ 8 | MODEL_ADM_Quantile("ADM_Quantile", "ADM_Quantile", "基于分位值的离群点监测"), 9 | /** GESD-based absolute value detection */ 10 | MODEL_ADM_GESD("ADM_GESD", "ADM_GESD", "基于GESD的离群值检测"), 11 | /** Volatility-based volatility anomaly detection */ 12 | MODEL_ADM_2ndDerivationMBP("ADM_2ndDerivationMBP", "ADM_2ndDerivationMBP", "基于波动率的波动异常检测"), 13 | /** Mann-Kendall based trend anomaly detection */ 14 | MODEL_ADM_ManKendall("ADM_ManKendall", "ADM_ManKendall", "基于Mann-Kendall的趋势异常检测"), 15 | /** Threshold-based anomaly detection */ 16 | MODEL_ADM_ThresholdRule("ADM_ThresholdRule", "ADM_ThresholdRule", "基于阈值规则的异常检测"), 17 | /** Z-Score anomaly detection */ 18 | MODEL_ADM_ZScore("ADM_ZScore", "ADM_ZScore", "基于Z-score的异常检测"), 19 | 20 | 21 | //=================INFLUENCE======================// 22 | /** Influence Positive */ 23 | INFLUENCE_POSITIVE("1", "Influence Positive", "正向影响"), 24 | /** Influence Negative */ 25 | INFLUENCE_NEGATIVE("-1", "Influence Negative", "负向影响"), 26 | 27 | 28 | //=================TREND======================// 29 | /** Trend No Obvious */ 30 | TREND_NO_OBVIOUS("0", "Trend No Obvious", "无明显趋势"), 31 | 32 | /** Trend Up */ 33 | TREND_UP("1", "Trend Up", "向上趋势"), 34 | /** Trend Down */ 35 | TREND_DOWN("-1", "Trend Down", "向下趋势"), 36 | 37 | 38 | //=================TYPE======================// 39 | 40 | /** Outliers Anomaly */ 41 | TYPE_OUTLIERS_VALUE("1", "Outliers Anomaly", "绝对值异常"), 42 | /** Fluctuation Anomaly */ 43 | TYPE_FLUCTUATION("2", "Fluctuation Anomaly", "波动异常"), 44 | /** Trend Anomaly */ 45 | TYPE_TREND("3", "Trend Anomaly", "趋势异常"), 46 | /** Threshold Anomaly */ 47 | TYPE_THRESHOLD("10", "Threshold Anomaly", "阈值异常"), 48 | 49 | ; 50 | 51 | AnomalyDictType(String code, String enName, String zhName) { 52 | this.code = code; 53 | this.enName = enName; 54 | this.zhName = zhName; 55 | } 56 | 57 | private String code; 58 | private String enName; 59 | private String zhName; 60 | 61 | public String getCode() { 62 | return code; 63 | } 64 | 65 | public String getEnName() { 66 | return enName; 67 | } 68 | 69 | public String getZhName() { 70 | return zhName; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/enumtype/CompareType.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.enumtype; 2 | 3 | public enum CompareType { 4 | /** 5 | * = 6 | */ 7 | EQ, 8 | /** 9 | * > 10 | */ 11 | GREATER, 12 | /** 13 | * >= 14 | */ 15 | GREATER_OR_EQ, 16 | /** 17 | * < 18 | */ 19 | LESS, 20 | /** 21 | * <= 22 | */ 23 | LESS_OR_EQ; 24 | 25 | public static CompareType parse(String signal) { 26 | if (">".equals(signal)) { 27 | return GREATER; 28 | } else if (">=".equals(signal) || "GREATER_OR_EQUAL".equalsIgnoreCase(signal) || "GREATER_OR_EQ".equalsIgnoreCase(signal)) { 29 | return GREATER_OR_EQ; 30 | } else if ("=".equals(signal) || "==".equals(signal) || "EQUAL".equalsIgnoreCase(signal) || "EQ".equalsIgnoreCase(signal)) { 31 | return EQ; 32 | } else if ("<".equals(signal) || "LESS".equalsIgnoreCase(signal)) { 33 | return LESS; 34 | } else if ("<=".equals(signal) || "LESS_THAN_OR_EQUAL".equalsIgnoreCase(signal) || "LESS_THAN_OR_EQ".equalsIgnoreCase(signal)) { 35 | return LESS_OR_EQ; 36 | } 37 | throw new IllegalArgumentException("[" + signal + "] signal illegal! Please input >,>=,=,==,<,<=."); 38 | } 39 | 40 | public boolean compare(Double current, Double signal) { 41 | if (this == EQ) { 42 | return current.compareTo(signal) == 0; 43 | } else if (this == GREATER) { 44 | return current.compareTo(signal) > 0; 45 | } else if (this == GREATER_OR_EQ) { 46 | return current.compareTo(signal) >= 0; 47 | } else if (this == LESS) { 48 | return current.compareTo(signal) < 0; 49 | } else if (this == LESS_OR_EQ) { 50 | return current.compareTo(signal) <= 0; 51 | } 52 | return false; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/enumtype/LogicType.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.enumtype; 2 | 3 | public enum LogicType { 4 | AND,OR 5 | ; 6 | 7 | public static LogicType parse(String signal){ 8 | if("and".equalsIgnoreCase(signal) || "&&".equals(signal)){ 9 | return AND; 10 | } 11 | else if("or".equalsIgnoreCase(signal) || "||".equals(signal)){ 12 | return OR; 13 | } 14 | throw new IllegalArgumentException("["+signal+"] signal illegal! Please input and,&&,or,||."); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/enumtype/ThresholdType.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.enumtype; 2 | 3 | public enum ThresholdType { 4 | 5 | /** 6 | * constant 7 | */ 8 | CONSTANT, 9 | 10 | /** 11 | * standard deviation 12 | */ 13 | STD, 14 | 15 | /** 16 | * variance 17 | */ 18 | VAR, 19 | 20 | /** 21 | * mean 22 | */ 23 | MEAN, 24 | 25 | /** 26 | * min 27 | */ 28 | MIN, 29 | 30 | /** 31 | * max 32 | */ 33 | MAX, 34 | /** 35 | * quantile 36 | */ 37 | QUANTILE 38 | ; 39 | 40 | public static ThresholdType parse(String signal){ 41 | if("constant".equalsIgnoreCase(signal)){ 42 | return CONSTANT; 43 | } 44 | else if("std".equalsIgnoreCase(signal)){ 45 | return STD; 46 | } 47 | else if("var".equalsIgnoreCase(signal)){ 48 | return VAR; 49 | } 50 | else if("mean".equalsIgnoreCase(signal)){ 51 | return MEAN; 52 | } 53 | else if("min".equalsIgnoreCase(signal)){ 54 | return MIN; 55 | } 56 | else if("max".equalsIgnoreCase(signal)){ 57 | return MAX; 58 | } 59 | else if("quantile".equalsIgnoreCase(signal)){ 60 | return QUANTILE; 61 | } 62 | 63 | throw new IllegalArgumentException("["+signal+"] signal illegal!"); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/ADM_2ndDerivationMBP.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.pojo.*; 6 | import org.algorithmtools.ad4j.utils.BandwidthUtil; 7 | import org.algorithmtools.ad4j.utils.CollectionUtil; 8 | import org.algorithmtools.ad4j.utils.IndicatorCalculateUtil; 9 | import org.algorithmtools.ad4j.utils.IndicatorSeriesUtil; 10 | import smile.stat.distribution.KernelDensity; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | /** 17 | * Anomaly detection model: Based on abnormal detection of volatility fluctuations 18 | * 19 | * @author mym 20 | */ 21 | public class ADM_2ndDerivationMBP extends AbstractADM { 22 | 23 | /** 24 | * min point size 25 | */ 26 | private int minPoints; 27 | /** 28 | * 2nd derivation threshold, filter then get significance anomaly candidate points. 29 | */ 30 | private double thresholdFactor; 31 | /** 32 | * Evaluate algorithm types: 33 | *
1. Distribution by volatility. That is, looking for anomalies based on the volatility distribution of the data 34 | *
2. By absolute volatility. That is, the point at which volatility outliers are found is the volatility point 35 | */ 36 | private int evaluateType; 37 | 38 | public ADM_2ndDerivationMBP() { 39 | super(AnomalyDictType.MODEL_ADM_2ndDerivationMBP, AnomalyDictType.TYPE_FLUCTUATION); 40 | } 41 | 42 | @Override 43 | public void init(AnomalyDetectionContext context) { 44 | this.minPoints = 3; 45 | this.thresholdFactor = Double.parseDouble((String) context.getConfig(ADMConfigs.ADM_2ED_DERIVATION_MBP_THRESHOLD_FACTOR)); 46 | this.evaluateType = Integer.parseInt((String) context.getConfig(ADMConfigs.ADM_2ED_DERIVATION_MBP_EVALUATE_TYPE)); 47 | } 48 | 49 | @Override 50 | public IndicatorEvaluateInfo evaluate(List indicatorSeries, AnomalyDetectionLog log) { 51 | List anomalyList = null; 52 | if (this.evaluateType == 1) { 53 | anomalyList = evaluateWithVolatilityThreshold(indicatorSeries); 54 | } else if (this.evaluateType == 2) { 55 | anomalyList = evaluateWithQuantile(indicatorSeries); 56 | } 57 | 58 | IndicatorEvaluateInfo result = buildDefaultEvaluateInfo(); 59 | if (CollectionUtil.isNotEmpty(anomalyList)) { 60 | result.setHasAnomaly(true); 61 | result.setAnomalySeriesList(anomalyList); 62 | return result; 63 | } 64 | return result; 65 | } 66 | 67 | @Override 68 | public boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log) { 69 | if (indicatorSeries.size() < minPoints) { 70 | log.warn(this.anomalyDetectionModel + ": indicator series size less then " + minPoints); 71 | return false; 72 | } 73 | return true; 74 | } 75 | 76 | /** 77 | * Through volatility threshold evaluate anomaly points 78 | * 79 | * @param data 80 | * @return {@link List}<{@link IndicatorSeries}> 81 | */ 82 | private List evaluateWithVolatilityThreshold(List data) { 83 | // volatility 84 | double[] volatility = calculateVolatility(data.stream().mapToDouble(IndicatorSeries::getValue).toArray()); 85 | 86 | // volatility to distribution points 87 | double[][] distributionPoints = transferToDistributionPoints(volatility, 100, thresholdFactor); 88 | double[] x = distributionPoints[0]; 89 | double[] y = distributionPoints[1]; 90 | 91 | // secondDerivatives 92 | double[] secondDerivatives = calculateSecondDerivatives(x, y); 93 | 94 | // find inflection point from 0 axis left and right, as min and max volatility threshold 95 | int[] inflectionPoints = calculateVolatilityThreshold(secondDerivatives, x, y); 96 | double minBound = x[inflectionPoints[0]]; 97 | double maxBound = x[inflectionPoints[1]]; 98 | 99 | // lowerBound-upperBound filter and transfer to result 100 | List anomalyList = new ArrayList<>(); 101 | int continualMaxIndex = -1; 102 | int continualLastIndex = -1; 103 | for (int i = 0; i < volatility.length; i++) { 104 | if (volatility[i] > maxBound || volatility[i] < minBound) { 105 | // continual isotropic volatilises, select the largest 106 | if (continualLastIndex < 0) { 107 | continualMaxIndex = i; 108 | continualLastIndex = i; 109 | } else if (i - 1 == continualLastIndex && volatility[i] * volatility[continualLastIndex] > 0) { 110 | if (Math.abs(volatility[i]) > Math.abs(volatility[continualMaxIndex])) { 111 | continualMaxIndex = i; 112 | } 113 | continualLastIndex = i; 114 | } else { 115 | anomalyList.add(new AnomalyIndicatorSeries(volatility[continualMaxIndex] > 0 ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, data.get(continualMaxIndex))); 116 | continualLastIndex = i; 117 | continualMaxIndex = i; 118 | } 119 | } 120 | } 121 | if (continualMaxIndex >= 0) { 122 | anomalyList.add(new AnomalyIndicatorSeries(volatility[continualMaxIndex] > 0 ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, data.get(continualMaxIndex))); 123 | } 124 | 125 | return anomalyList; 126 | } 127 | 128 | /** 129 | * Through volatility quantile to evaluate anomaly points 130 | * 131 | * @param data 132 | * @return {@link List}<{@link IndicatorSeries}> 133 | */ 134 | private List evaluateWithQuantile(List data) { 135 | double[] volatility = calculateVolatility(data.stream().mapToDouble(IndicatorSeries::getValue).toArray()); 136 | // lowerBound-upperBound filter 137 | double[] quantileIQR = IndicatorCalculateUtil.quantileBound(IndicatorSeriesUtil.transferFromArray(volatility), this.thresholdFactor * 1, 0.25, 0.75); 138 | double lowerBound = quantileIQR[0]; 139 | double upperBound = quantileIQR[1]; 140 | List anomalyList = new ArrayList<>(); 141 | for (int i = 0; i < volatility.length; i++) { 142 | if (volatility[i] > upperBound || volatility[i] < lowerBound) { 143 | anomalyList.add(new AnomalyIndicatorSeries(volatility[i] > 0 ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, data.get(i))); 144 | } 145 | } 146 | return anomalyList; 147 | } 148 | 149 | public double[] calculateVolatility(double[] data) { 150 | double[] volatility = new double[data.length]; 151 | volatility[0] = 0; 152 | for (int i = 1; i < data.length; i++) { 153 | volatility[i] = (data[i] - data[i - 1]) / data[i - 1]; 154 | } 155 | return volatility; 156 | } 157 | 158 | public double[][] transferToDistributionPoints(double[] data, int points, double bandwidthFactor) { 159 | double[] copyData = Arrays.copyOf(data, data.length); 160 | KernelDensity volatilityDistribution = new KernelDensity(copyData, bandwidthFactor * BandwidthUtil.calculateBandwidth(copyData)); 161 | 162 | double min = Arrays.stream(copyData).min().orElse(0.0); 163 | double max = Arrays.stream(copyData).max().orElse(1.0); 164 | double step = (max - min) / points; 165 | 166 | double[] x = new double[points]; 167 | double[] y = new double[points]; 168 | for (int i = 0; i < points; i++) { 169 | x[i] = min + i * step; 170 | y[i] = volatilityDistribution.p(x[i]); // KDE estimate 171 | } 172 | 173 | return new double[][]{x, y}; 174 | } 175 | 176 | public double[] calculateSecondDerivatives(double[] x, double[] y) { 177 | int n = x.length; 178 | if (n < 3) { 179 | throw new IllegalArgumentException("At least three points are required to compute second derivatives."); 180 | } 181 | 182 | double h; 183 | double[] secondDerivatives = new double[n]; 184 | // Compute second derivatives 185 | for (int i = 0; i < n; i++) { 186 | h = x[1] - x[0]; 187 | if (i == 0) { 188 | // Left boundary 189 | secondDerivatives[i] = (y[i + 2] - 2 * y[i + 1] + y[i]) / (h * h); 190 | } else if (i == n - 1) { 191 | // Right boundary 192 | secondDerivatives[i] = (y[i] - 2 * y[i - 1] + y[i - 2]) / (h * h); 193 | } else { 194 | // Internal points 195 | secondDerivatives[i] = (y[i + 1] - 2 * y[i] + y[i - 1]) / (h * h); 196 | } 197 | } 198 | 199 | return secondDerivatives; 200 | } 201 | 202 | // find inflection point from 0 axis left and right, as min and max volatility threshold 203 | public static int[] calculateVolatilityThreshold(double[] secondDerivative, double[] distributionX, double[] distributionY) { 204 | // MBP is max bend point 205 | int xAxisLeftMBPIndex = 0; 206 | double xAxisLeftMBPMaxDistance = -1; 207 | int xAxisRightMBPIndex = distributionX.length - 1; 208 | double xAxisRightMBPMaxDistance = -1; 209 | for (int i = 1; i < secondDerivative.length; i++) { 210 | // A change in sign could be an inflection point 211 | if (secondDerivative[i - 1] * secondDerivative[i] < 0) { 212 | double distance = Math.sqrt(Math.pow(distributionX[i], 2) + Math.pow(distributionY[i], 2)); 213 | if (distributionX[i] >= 0) { 214 | // zero axis right 215 | if (distance >= xAxisRightMBPMaxDistance) { 216 | xAxisRightMBPIndex = i; 217 | xAxisRightMBPMaxDistance = distance; 218 | } 219 | } else { 220 | // zero axis left 221 | if (distance >= xAxisLeftMBPMaxDistance) { 222 | xAxisLeftMBPIndex = i; 223 | xAxisLeftMBPMaxDistance = distance; 224 | } 225 | } 226 | } 227 | } 228 | 229 | return new int[]{xAxisLeftMBPIndex, xAxisRightMBPIndex}; 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/ADM_GESD.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.pojo.*; 6 | import org.algorithmtools.ad4j.utils.CollectionUtil; 7 | import org.algorithmtools.ad4j.utils.IndicatorCalculateUtil; 8 | import org.apache.commons.math3.distribution.TDistribution; 9 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | /** 16 | * Anomaly detection model: Outlier detection based on GESD 17 | * @author mym 18 | */ 19 | public class ADM_GESD extends AbstractADM { 20 | 21 | private double alpha; 22 | 23 | public ADM_GESD() { 24 | super(AnomalyDictType.MODEL_ADM_GESD, AnomalyDictType.TYPE_OUTLIERS_VALUE); 25 | } 26 | 27 | @Override 28 | public void init(AnomalyDetectionContext context) { 29 | this.alpha = Double.parseDouble((String) context.getConfig(ADMConfigs.ADM_GESD_ALPHA)); 30 | } 31 | 32 | @Override 33 | public IndicatorEvaluateInfo evaluate(List indicatorSeries, AnomalyDetectionLog log) { 34 | List gesdIndexList = gesd(indicatorSeries, indicatorSeries.size() / 2, alpha); 35 | 36 | // build evaluate info 37 | IndicatorEvaluateInfo result = buildDefaultEvaluateInfo(); 38 | if (CollectionUtil.isNotEmpty(gesdIndexList)) { 39 | result.setHasAnomaly(true); 40 | result.setNormalRangeMin(0d); 41 | result.setNormalRangeMax(0d); 42 | 43 | DescriptiveStatistics statistics = IndicatorCalculateUtil.initStatistic(null, indicatorSeries, null); 44 | final double mean = statistics.getMean(); 45 | result.setAnomalySeriesList(gesdIndexList.stream() 46 | .map(v -> new AnomalyIndicatorSeries(indicatorSeries.get(v).getValue() > mean ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, indicatorSeries.get(v))) 47 | .collect(Collectors.toList()) 48 | ); 49 | } 50 | 51 | return result; 52 | } 53 | 54 | @Override 55 | public boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log) { 56 | // TODO 检查是否是正态分布 57 | return true; 58 | } 59 | 60 | protected List gesd(List dataList, int maxOutliers, double alpha) { 61 | List outlierIndexes = new ArrayList<>(); 62 | List originalData = new ArrayList<>(dataList); 63 | 64 | DescriptiveStatistics stats = null; 65 | int n = dataList.size(); 66 | int r = maxOutliers; 67 | for (int i = 1; i <= r; i++) { // i = 1,2,3...r 68 | // init statistic 69 | stats = IndicatorCalculateUtil.initStatistic(stats, originalData, outlierIndexes); 70 | // calculate mean and std 71 | double mean = stats.getMean(); 72 | double stdDev = stats.getStandardDeviation(); 73 | 74 | // calculate Max(Ri) 75 | double maxRi = -1; 76 | int maxRiIndex = -1; 77 | for (int j = 0; j < originalData.size(); j++) { 78 | if (outlierIndexes.contains(j)) { 79 | continue; 80 | } 81 | 82 | double deviation = Math.abs(originalData.get(j).getValue() - mean); 83 | if (maxRi < deviation) { 84 | maxRi = deviation; 85 | maxRiIndex = j; 86 | } 87 | } 88 | 89 | // Lambda i 90 | double lambdaI = calculateLambdaI(n, i, alpha); 91 | 92 | // judge anomaly: where(Ri > LambdaI) 93 | if ((maxRi / stdDev) > lambdaI) { 94 | outlierIndexes.add(maxRiIndex); 95 | } else { 96 | break; 97 | } 98 | } 99 | 100 | return outlierIndexes; 101 | } 102 | 103 | // calculate lambda (critical-value) 104 | public double calculateLambdaI(int n, int i, double alpha) { 105 | TDistribution tDistribution = new TDistribution(n - i - 1); 106 | double p = 1 - alpha / (2 * (n - i + 1)); // both sid t-distribution 107 | double tValue = tDistribution.inverseCumulativeProbability(p); // Probability to T 108 | return (n - i) * tValue / Math.sqrt((n - i - 1 + Math.pow(tValue, 2)) * (n - i + 1)); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/ADM_MannKendall.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.pojo.AnomalyDetectionContext; 6 | import org.algorithmtools.ad4j.pojo.AnomalyDetectionLog; 7 | import org.algorithmtools.ad4j.pojo.IndicatorEvaluateInfo; 8 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 9 | import org.algorithmtools.ad4j.utils.IndicatorCalculateUtil; 10 | 11 | import java.util.List; 12 | import java.util.Objects; 13 | 14 | /** 15 | * Anomaly detection model: Based on Mann-Kendall trend anomaly detection 16 | * @author mym 17 | */ 18 | public class ADM_MannKendall extends AbstractADM { 19 | 20 | /** significance level P to Z-critical value */ 21 | private double criticalZ; 22 | 23 | public ADM_MannKendall() { 24 | super(AnomalyDictType.MODEL_ADM_ManKendall, AnomalyDictType.TYPE_TREND); 25 | } 26 | 27 | @Override 28 | public void init(AnomalyDetectionContext context) { 29 | // this.criticalZ = 1.96; 30 | this.criticalZ = Double.parseDouble((String) context.getConfig(ADMConfigs.ADM_MANNKENDALL_CRITICALZ)); 31 | } 32 | 33 | @Override 34 | public IndicatorEvaluateInfo evaluate(List data, AnomalyDetectionLog log) { 35 | List indicatorSeries = IndicatorCalculateUtil.excludeOutlier(data); 36 | 37 | MannKendallResult mannKendallResult = mannKendall(indicatorSeries, log); 38 | 39 | // build evaluate info 40 | IndicatorEvaluateInfo result = buildDefaultEvaluateInfo(); 41 | if (!Objects.equals(mannKendallResult.getTrend(), AnomalyDictType.TREND_NO_OBVIOUS.getCode())) { 42 | result.setHasAnomaly(true); 43 | result.setNormalRangeMin(0d); 44 | result.setNormalRangeMax(0d); 45 | result.setAnomalyTrend(Objects.equals(mannKendallResult.getTrend(), AnomalyDictType.TREND_UP.getCode()) ? AnomalyDictType.TREND_UP : 46 | AnomalyDictType.TREND_DOWN); 47 | } 48 | return result; 49 | } 50 | 51 | @Override 52 | public boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log) { 53 | return true; 54 | } 55 | 56 | private MannKendallResult mannKendall(List indicatorSeries, AnomalyDetectionLog log) { 57 | int n = indicatorSeries.size(); 58 | 59 | // calculate S 60 | int S = 0; 61 | for (int i = 0; i < n - 1; i++) { 62 | for (int j = i + 1; j < n; j++) { 63 | S += Double.compare(indicatorSeries.get(j).getValue(), indicatorSeries.get(i).getValue()); // sign(X_j - X_i) 64 | } 65 | } 66 | 67 | // calculate Var(S) 68 | double varianceS = n * (n - 1) * (2 * n + 5) / 18.0; 69 | 70 | // calculate Z 71 | double Z; 72 | if (S > 0) { 73 | Z = (S - 1) / Math.sqrt(varianceS); 74 | } else if (S == 0) { 75 | Z = 0; 76 | } else { 77 | Z = (S + 1) / Math.sqrt(varianceS); 78 | } 79 | 80 | // judge trend with Z 81 | String trend; 82 | double alpha = criticalZ; 83 | if (Math.abs(Z) > alpha) { 84 | if (Z > 0) { 85 | trend = AnomalyDictType.TREND_UP.getCode(); 86 | } else { 87 | trend = AnomalyDictType.TREND_DOWN.getCode(); 88 | } 89 | } else { 90 | trend = AnomalyDictType.TREND_NO_OBVIOUS.getCode(); 91 | } 92 | 93 | return new MannKendallResult(S, varianceS, Z, trend); 94 | } 95 | 96 | private class MannKendallResult { 97 | private final int S; 98 | private final double varianceS; 99 | private final double Z; 100 | private final String trend; 101 | 102 | public MannKendallResult(int S, double varianceS, double Z, String trend) { 103 | this.S = S; 104 | this.varianceS = varianceS; 105 | this.Z = Z; 106 | this.trend = trend; 107 | } 108 | 109 | public int getS() { 110 | return S; 111 | } 112 | 113 | public double getVarianceS() { 114 | return varianceS; 115 | } 116 | 117 | public double getZ() { 118 | return Z; 119 | } 120 | 121 | public String getTrend() { 122 | return trend; 123 | } 124 | } 125 | } 126 | 127 | 128 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/ADM_Quantile.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.pojo.*; 6 | import org.algorithmtools.ad4j.utils.CollectionUtil; 7 | import org.algorithmtools.ad4j.utils.IndicatorCalculateUtil; 8 | 9 | import java.util.List; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * Anomaly detection model: anomaly detection based on quantile (boxplot) 14 | * @author mym 15 | */ 16 | public class ADM_Quantile extends AbstractADM { 17 | 18 | /** 19 | * IQR weight multiplier 20 | */ 21 | private double iqrMultiplier; 22 | 23 | public ADM_Quantile() { 24 | super(AnomalyDictType.MODEL_ADM_Quantile, AnomalyDictType.TYPE_OUTLIERS_VALUE); 25 | } 26 | 27 | @Override 28 | public void init(AnomalyDetectionContext context) { 29 | this.iqrMultiplier = Double.parseDouble((String) context.getConfig(ADMConfigs.ADM_QUANTILE_IQR_MULTIPLIER)); 30 | } 31 | 32 | @Override 33 | public IndicatorEvaluateInfo evaluate(List data, AnomalyDetectionLog log) { 34 | // calculate bound 35 | double[] quantileBound = IndicatorCalculateUtil.quantileBound(data, iqrMultiplier, 0.25, 0.75); 36 | double lowerBound = quantileBound[0]; 37 | double upperBound = quantileBound[1]; 38 | 39 | // find anomaly indicator series 40 | List anomalyList = data.stream() 41 | .filter(v -> v.getValue() > upperBound || v.getValue() < lowerBound) 42 | .map(v -> new AnomalyIndicatorSeries(v.getValue() > upperBound ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, v)) 43 | .collect(Collectors.toList()); 44 | 45 | // build evaluate info 46 | IndicatorEvaluateInfo result = buildDefaultEvaluateInfo(); 47 | if (CollectionUtil.isNotEmpty(anomalyList)) { 48 | result.setHasAnomaly(true); 49 | result.setNormalRangeMin(lowerBound); 50 | result.setNormalRangeMax(upperBound); 51 | result.setAnomalySeriesList(anomalyList); 52 | return result; 53 | } 54 | 55 | return result; 56 | } 57 | 58 | @Override 59 | public boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log) { 60 | return true; 61 | } 62 | 63 | private double quantile(List sortedData, double quantile) { 64 | int n = sortedData.size(); 65 | double index = quantile * (n - 1); 66 | int lowerIndex = (int) Math.floor(index); 67 | int upperIndex = (int) Math.ceil(index); 68 | 69 | if (lowerIndex == upperIndex) { 70 | return sortedData.get(lowerIndex).getValue(); 71 | } 72 | 73 | double weight = index - lowerIndex; 74 | return (1 - weight) * sortedData.get(lowerIndex).getValue() + weight * sortedData.get(upperIndex).getValue(); 75 | } 76 | 77 | } 78 | 79 | 80 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/ADM_ThresholdRule.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.enumtype.LogicType; 6 | import org.algorithmtools.ad4j.enumtype.ThresholdType; 7 | import org.algorithmtools.ad4j.pojo.*; 8 | import org.algorithmtools.ad4j.utils.IndicatorCalculateUtil; 9 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; 10 | 11 | import java.util.List; 12 | import java.util.Objects; 13 | 14 | /** 15 | * Anomaly detection model: Threshold Rule 16 | *

Threshold rule matching

17 | * 18 | * @author mym 19 | */ 20 | public class ADM_ThresholdRule extends AbstractADM { 21 | private ThresholdRuleBase thresholdRule = null; 22 | 23 | public ADM_ThresholdRule() { 24 | super(AnomalyDictType.MODEL_ADM_ThresholdRule, AnomalyDictType.TYPE_THRESHOLD); 25 | } 26 | 27 | @Override 28 | public void init(AnomalyDetectionContext context) { 29 | Object config = context.getConfig(ADMConfigs.ADM_THRESHOLD_RULE_SET); 30 | this.thresholdRule = Objects.isNull(config) ? null : (ThresholdRuleBase) config; 31 | } 32 | 33 | @Override 34 | public IndicatorEvaluateInfo evaluate(List indicatorSeries, AnomalyDetectionLog log) { 35 | if (this.thresholdRule == null) { 36 | return null; 37 | } 38 | 39 | IndicatorEvaluateInfo result = buildDefaultEvaluateInfo(); 40 | DescriptiveStatistics stats = IndicatorCalculateUtil.initStatistic(null, indicatorSeries, null); 41 | double mean = stats.getMean(); 42 | List sortList = IndicatorCalculateUtil.sortAsc(indicatorSeries); 43 | for (IndicatorSeries indicator : indicatorSeries) { 44 | boolean hasAnomaly = judgeHasAnomaly(sortList, stats, this.thresholdRule, indicator); 45 | if (hasAnomaly) { 46 | result.setHasAnomaly(hasAnomaly); 47 | result.add(new AnomalyIndicatorSeries(indicator.getValue() > mean ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, indicator)); 48 | } 49 | } 50 | 51 | return result; 52 | } 53 | 54 | @Override 55 | public boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log) { 56 | return true; 57 | } 58 | 59 | private boolean judgeHasAnomaly(List sortList, DescriptiveStatistics stats, ThresholdRuleBase currentRule, IndicatorSeries currentIndicator) { 60 | if (Objects.isNull(currentRule.getThresholdRules())) { 61 | ThresholdRule rule = (ThresholdRule) currentRule; 62 | return thresholdCompare(sortList, stats, rule, currentIndicator); 63 | } else { 64 | Boolean ruleGroupResult = null; 65 | ThresholdRuleGroup ruleGroup = (ThresholdRuleGroup) currentRule; 66 | for (ThresholdRuleBase rule : ruleGroup.getThresholdRules()) { 67 | boolean hasAnomaly = judgeHasAnomaly(sortList, stats, rule, currentIndicator); 68 | if (ruleGroup.getLogicType() == LogicType.AND) { 69 | ruleGroupResult = ruleGroupResult == null ? hasAnomaly : ruleGroupResult && hasAnomaly; 70 | } else if (ruleGroup.getLogicType() == LogicType.OR) { 71 | ruleGroupResult = ruleGroupResult == null ? hasAnomaly : ruleGroupResult || hasAnomaly; 72 | } 73 | } 74 | return Boolean.TRUE.equals(ruleGroupResult); 75 | } 76 | } 77 | 78 | private boolean thresholdCompare(List sortList, DescriptiveStatistics stats, ThresholdRule currentRule, IndicatorSeries currentIndicator) { 79 | return currentRule.getCompareType().compare(currentIndicator.getValue(), calcCompareValue(sortList, stats, currentRule)); 80 | } 81 | 82 | private Double calcCompareValue(List sortList, DescriptiveStatistics stats, ThresholdRule rule) { 83 | Double compareValue = null; 84 | if (rule.getThresholdType() == ThresholdType.CONSTANT) { 85 | compareValue = rule.getFactor(); 86 | } else if (rule.getThresholdType() == ThresholdType.MIN) { 87 | compareValue = rule.getFactor() * stats.getMin(); 88 | } else if (rule.getThresholdType() == ThresholdType.MAX) { 89 | compareValue = rule.getFactor() * stats.getMax(); 90 | } else if (rule.getThresholdType() == ThresholdType.MEAN) { 91 | compareValue = rule.getFactor() * stats.getMean(); 92 | } else if (rule.getThresholdType() == ThresholdType.STD) { 93 | compareValue = rule.getFactor() * stats.getStandardDeviation(); 94 | } else if (rule.getThresholdType() == ThresholdType.VAR) { 95 | compareValue = rule.getFactor() * stats.getVariance(); 96 | } else if (rule.getThresholdType() == ThresholdType.QUANTILE) { 97 | compareValue = IndicatorCalculateUtil.quantile(sortList, rule.getFactor()); 98 | } 99 | 100 | return compareValue; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/ADM_ZScore.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.config.ADMConfigs; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.pojo.*; 6 | import org.algorithmtools.ad4j.utils.IndicatorCalculateUtil; 7 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Anomaly detection model: Z-score 13 | *

Suitable for normal distribution, few outliers, outliers may affect the mean and standard deviation and thus the detection rate,high computational efficiency

14 | * @author mym 15 | */ 16 | public class ADM_ZScore extends AbstractADM { 17 | private double scoreThreshold = 2; 18 | public ADM_ZScore() { 19 | super(AnomalyDictType.MODEL_ADM_ZScore, AnomalyDictType.TYPE_OUTLIERS_VALUE); 20 | } 21 | 22 | @Override 23 | public void init(AnomalyDetectionContext context) { 24 | this.scoreThreshold = Double.parseDouble((String) context.getConfig(ADMConfigs.ADM_ZSCORE_THRESHOLD)); 25 | } 26 | 27 | @Override 28 | public IndicatorEvaluateInfo evaluate(List indicatorSeries, AnomalyDetectionLog log) { 29 | DescriptiveStatistics stats = IndicatorCalculateUtil.initStatistic(null, indicatorSeries, null); 30 | // calculate mean and std 31 | double mean = stats.getMean(); 32 | double stdDev = stats.getStandardDeviation(); 33 | IndicatorEvaluateInfo result = buildDefaultEvaluateInfo(); 34 | for (int i = 0; i < indicatorSeries.size(); i++) { 35 | double zScore = (indicatorSeries.get(i).getValue() - mean) / stdDev; 36 | if (Math.abs(zScore) > scoreThreshold) { 37 | IndicatorSeries anomalyIndicatorSeries = indicatorSeries.get(i); 38 | result.add(new AnomalyIndicatorSeries(anomalyIndicatorSeries.getValue() > mean ? AnomalyDictType.INFLUENCE_POSITIVE : AnomalyDictType.INFLUENCE_NEGATIVE, anomalyIndicatorSeries)); 39 | result.setHasAnomaly(true); 40 | } 41 | } 42 | 43 | // TODO calculate range 44 | result.setNormalRangeMin(0d); 45 | result.setNormalRangeMax(0d); 46 | return result; 47 | } 48 | 49 | @Override 50 | public boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log) { 51 | // TODO 正态分布检测 52 | return true; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/model/adm/AbstractADM.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model.adm; 2 | 3 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 4 | import org.algorithmtools.ad4j.pojo.AnomalyDetectionContext; 5 | import org.algorithmtools.ad4j.pojo.AnomalyDetectionLog; 6 | import org.algorithmtools.ad4j.pojo.IndicatorEvaluateInfo; 7 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Anomaly detection model abstract interface 13 | * @author mym 14 | */ 15 | public abstract class AbstractADM { 16 | 17 | /** 18 | * model name 19 | */ 20 | protected AnomalyDictType anomalyDetectionModel; 21 | 22 | /** 23 | * anomaly type 24 | */ 25 | protected AnomalyDictType anomalyType; 26 | 27 | public AbstractADM(AnomalyDictType anomalyDetectionModel, AnomalyDictType anomalyType) { 28 | this.anomalyDetectionModel = anomalyDetectionModel; 29 | this.anomalyType = anomalyType; 30 | } 31 | 32 | /** 33 | * init model 34 | * @param context context env 35 | */ 36 | public abstract void init(AnomalyDetectionContext context); 37 | 38 | /** 39 | * evaluate anomaly 40 | * @param indicatorSeries indicator series 41 | * @param log log 42 | */ 43 | public abstract IndicatorEvaluateInfo evaluate(List indicatorSeries, AnomalyDetectionLog log); 44 | 45 | /** 46 | * check compatibility 47 | * @param indicatorSeries indicator series 48 | * @param log log 49 | * @return boolean true/false 50 | */ 51 | public abstract boolean checkCompatibility(List indicatorSeries, AnomalyDetectionLog log); 52 | 53 | public IndicatorEvaluateInfo buildDefaultEvaluateInfo(){ 54 | return new IndicatorEvaluateInfo(anomalyDetectionModel, anomalyType, false); 55 | } 56 | 57 | public AnomalyDictType getAnomalyDetectionModel() { 58 | return anomalyDetectionModel; 59 | } 60 | 61 | public AnomalyDictType getAnomalyType() { 62 | return anomalyType; 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/AnomalyDetectionContext.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.algorithmtools.ad4j.config.ADMConfigs; 6 | import org.algorithmtools.ad4j.config.ConfigOption; 7 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 8 | import org.algorithmtools.ad4j.enumtype.CompareType; 9 | import org.algorithmtools.ad4j.enumtype.LogicType; 10 | import org.algorithmtools.ad4j.enumtype.ThresholdType; 11 | 12 | import java.io.Serializable; 13 | import java.math.BigDecimal; 14 | import java.util.*; 15 | 16 | public class AnomalyDetectionContext implements Serializable { 17 | 18 | private List configAnomalyDetectionModelList = new ArrayList<>(); 19 | 20 | public List getConfigAnomalyDetectionModelList() { 21 | return configAnomalyDetectionModelList; 22 | } 23 | 24 | public void addConfigAnomalyDetectionModel(AnomalyDictType anomalyDictType) { 25 | if (this.configAnomalyDetectionModelList == null){ 26 | this.configAnomalyDetectionModelList = new ArrayList<>(); 27 | } 28 | 29 | this.getConfigAnomalyDetectionModelList().add(anomalyDictType); 30 | } 31 | 32 | private Map admConfigMap = new HashMap<>(); 33 | 34 | public void putConfig(String key, Object value){ 35 | if (!ADMConfigs.configKeyMap.containsKey(key)) { 36 | throw new IllegalArgumentException("["+key+"] not exist!"); 37 | } 38 | putConfig(ADMConfigs.configKeyMap.get(key), value); 39 | } 40 | 41 | public void putConfig(ConfigOption configOption, Object value){ 42 | if(value != null && value.toString().endsWith("s")){ 43 | // end with "s", then sensitivityConvert 44 | String sensitivityValue = value.toString(); 45 | admConfigMap.put(configOption,sensitivityConvert(configOption, sensitivityValue.substring(0, sensitivityValue.length() - 1))); 46 | } else { 47 | admConfigMap.put(configOption, 48 | (value instanceof BigDecimal || value instanceof Double || value instanceof Float || value instanceof Integer) ? value.toString() : value); 49 | } 50 | } 51 | 52 | public Object getConfig(String key){ 53 | if (ADMConfigs.configKeyMap.containsKey(key)) { 54 | return getConfig(ADMConfigs.configKeyMap.get(key)); 55 | } else { 56 | return null; 57 | } 58 | } 59 | 60 | public Object getConfig(ConfigOption configOption){ 61 | return Optional.ofNullable(admConfigMap.get(configOption)).orElseGet(configOption::getDefaultValue); 62 | } 63 | 64 | public static AnomalyDetectionContext createDefault(){ 65 | AnomalyDetectionContext anomalyDetectionContext = new AnomalyDetectionContext(); 66 | defaultConfig(anomalyDetectionContext); 67 | return anomalyDetectionContext; 68 | } 69 | 70 | public static AnomalyDetectionContext create(JSONObject propertiesJson){ 71 | AnomalyDetectionContext anomalyDetectionContext = new AnomalyDetectionContext(); 72 | defaultConfig(anomalyDetectionContext); 73 | 74 | if(Objects.nonNull(propertiesJson)){ 75 | for (Map.Entry e : propertiesJson.entrySet()) { 76 | if (e.getKey().equalsIgnoreCase(ADMConfigs.ADM_THRESHOLD_RULE_SET.getKey())) { 77 | anomalyDetectionContext.putConfig(e.getKey(), generalThresholdRule((JSONObject) e.getValue())); 78 | } else { 79 | anomalyDetectionContext.putConfig(e.getKey(), e.getValue()); 80 | } 81 | } 82 | } 83 | 84 | return anomalyDetectionContext; 85 | } 86 | 87 | /** 88 | * data case:{"logicType":"and","ruleGroup":[{"factor":100,"thresholdType":"std","compareType":">"}]} 89 | * @param thresholdRuleSetJson adm.threshold_rule.set config json 90 | * @return ThresholdRuleBase 91 | */ 92 | public static ThresholdRuleBase generalThresholdRule(JSONObject thresholdRuleSetJson) { 93 | if (Objects.isNull(thresholdRuleSetJson)) { 94 | return null; 95 | } 96 | 97 | if (thresholdRuleSetJson.containsKey("ruleGroup")) { 98 | ThresholdRuleGroup ruleGroup = new ThresholdRuleGroup(LogicType.parse(thresholdRuleSetJson.getString("logicType"))); 99 | JSONArray jsonArr = thresholdRuleSetJson.getJSONArray("ruleGroup"); 100 | for (int i = 0; i < jsonArr.size(); i++) { 101 | ThresholdRuleBase rb = generalThresholdRule(jsonArr.getJSONObject(i)); 102 | if(Objects.nonNull(rb)){ 103 | ruleGroup.addRules(rb); 104 | } 105 | } 106 | return ruleGroup; 107 | } else { 108 | return new ThresholdRule(ThresholdType.parse(thresholdRuleSetJson.getString("thresholdType")), CompareType.parse(thresholdRuleSetJson.getString("compareType")), thresholdRuleSetJson.getDouble("factor")); 109 | } 110 | } 111 | 112 | private static void defaultConfig(AnomalyDetectionContext anomalyDetectionContext) { 113 | for (Map.Entry entry : ADMConfigs.configKeyMap.entrySet()) { 114 | anomalyDetectionContext.putConfig(entry.getValue(), entry.getValue().getDefaultValue()); 115 | } 116 | } 117 | 118 | /** 119 | * Some anomaly model support the specification of configuration values in the form of sensitivities. If not support, return config default value. 120 | * 121 | * @param config 122 | * @param sensitivity 0.1-> low sensitivity; 0.05-> normal sensitivity; 0.01-> high sensitivity 123 | * @return config value 124 | */ 125 | public static String sensitivityConvert(ConfigOption config, String sensitivity) { 126 | if (config == null) { 127 | return null; 128 | } 129 | if (config.getKey().equals(ADMConfigs.ADM_QUANTILE_IQR_MULTIPLIER.getKey())) { 130 | if ("0.1".equals(sensitivity)) { 131 | return "3.0"; // confidence level 90% (more bigger then detection more less) 132 | } else if ("0.05".equals(sensitivity)) { 133 | return "1.5"; // confidence level 95% 134 | } else if ("0.01".equals(sensitivity)) { 135 | return "1.0"; // confidence level 99% 136 | } 137 | } 138 | if (config.getKey().equals(ADMConfigs.ADM_ZSCORE_THRESHOLD.getKey())) { 139 | if ("0.1".equals(sensitivity)) { 140 | return "1.5"; // confidence level 86.6% (more bigger then detection more less) 141 | } else if ("0.05".equals(sensitivity)) { 142 | return "2.0"; // confidence level 95% 143 | } else if ("0.01".equals(sensitivity)) { 144 | return "3.0"; // confidence level 99.7% 145 | } 146 | } 147 | if (config.getKey().equals(ADMConfigs.ADM_GESD_ALPHA.getKey())) { 148 | if ("0.1".equals(sensitivity)) { 149 | return "0.1"; // confidence level 90% (more bigger then detection more less) 150 | } else if ("0.05".equals(sensitivity)) { 151 | return "0.05"; // confidence level 95% 152 | } else if ("0.01".equals(sensitivity)) { 153 | return "0.01"; // confidence level 99% 154 | } 155 | } 156 | if (config.getKey().equals(ADMConfigs.ADM_2ED_DERIVATION_MBP_THRESHOLD_FACTOR.getKey())) { 157 | // only support when ADM_2ED_DERIVATION_MBP_EVALUATE_TYPE = 1 158 | if ("0.1".equals(sensitivity)) { 159 | return "3.0"; // fitting 99.7%(more bigger then detection more less) 160 | } else if ("0.05".equals(sensitivity)) { 161 | return "2.0"; // fitting 95% 162 | } else if ("0.01".equals(sensitivity)) { 163 | return "1.5"; // fitting 86.6% 164 | } 165 | } 166 | if (config.getKey().equals(ADMConfigs.ADM_MANNKENDALL_CRITICALZ.getKey())) { 167 | if ("0.1".equals(sensitivity)) { 168 | return "1.64"; // confidence level 90% (more bigger then detection more less) 169 | } else if ("0.05".equals(sensitivity)) { 170 | return "1.96"; // confidence level 95% 171 | } else if ("0.01".equals(sensitivity)) { 172 | return "2.58"; // confidence level 99% 173 | } 174 | } 175 | 176 | return String.valueOf(config.getDefaultValue()); 177 | } 178 | 179 | 180 | } 181 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/AnomalyDetectionLog.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.LinkedList; 7 | 8 | public class AnomalyDetectionLog { 9 | 10 | private static final Logger LOGGER = LoggerFactory.getLogger(AnomalyDetectionLog.class); 11 | 12 | private final LinkedList logList; 13 | 14 | public AnomalyDetectionLog() { 15 | this.logList = new LinkedList<>(); 16 | } 17 | 18 | public void add(String level, String topic, String text){ 19 | this.logList.add(new Log(level,"[" + topic + "]", text)); 20 | } 21 | 22 | public void info(String msg){ 23 | LOGGER.info(msg); 24 | } 25 | 26 | public void error(String msg){ 27 | LOGGER.error(msg); 28 | } 29 | 30 | public void warn(String msg){ 31 | LOGGER.warn(msg); 32 | } 33 | 34 | public void print(){ 35 | for (Log log : logList) { 36 | if("debug".equals(log.level)){ 37 | LOGGER.debug("{}: {}", log.topic, log.text); 38 | } else if("warn".equals(log.level)){ 39 | LOGGER.warn("{}: {}", log.topic, log.text); 40 | } else if("error".equals(log.level)){ 41 | LOGGER.error("{}: {}", log.topic, log.text); 42 | } else { 43 | LOGGER.info("{}: {}", log.topic, log.text); 44 | } 45 | } 46 | } 47 | 48 | public static class Log{ 49 | private String level; 50 | private String topic; 51 | private String text; 52 | 53 | public Log(String level, String topic, String text) { 54 | this.level = level; 55 | this.topic = topic; 56 | this.text = text; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/AnomalyDetectionProcessCollector.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.algorithmtools.ad4j.model.adm.AbstractADM; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | import java.util.Objects; 8 | 9 | public class AnomalyDetectionProcessCollector { 10 | private AnomalyDetectionLog log; 11 | private AnomalyDetectionContext context; 12 | private LinkedHashMap processLinked; 13 | 14 | public AnomalyDetectionProcessCollector(AnomalyDetectionContext context) { 15 | this.log = new AnomalyDetectionLog(); 16 | this.context = context; 17 | this.processLinked = new LinkedHashMap<>(8); 18 | } 19 | 20 | public void addProcess(AbstractADM anomalyDetection){ 21 | this.processLinked.put(anomalyDetection, null); 22 | } 23 | 24 | public boolean hasAnomaly(){ 25 | if(Objects.isNull(processLinked) || processLinked.isEmpty()){ 26 | return false; 27 | } 28 | for (Map.Entry entry : processLinked.entrySet()) { 29 | if(entry.getValue() != null){ 30 | return true; 31 | } 32 | } 33 | return false; 34 | } 35 | 36 | public AnomalyDetectionLog getLog() { 37 | return log; 38 | } 39 | 40 | public AnomalyDetectionContext getContext() { 41 | return context; 42 | } 43 | 44 | public LinkedHashMap getProcessLinked() { 45 | return processLinked; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/AnomalyDetectionResult.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.Map; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | 10 | /** 11 | * Anomaly detection result pojo 12 | */ 13 | public class AnomalyDetectionResult implements Serializable { 14 | 15 | /** 16 | * indicator info 17 | */ 18 | private IndicatorInfo indicatorInfo; 19 | 20 | /** indicator evaluate result. key=AnomalyType, value=List */ 21 | public Map> indicatorEvaluateMap = new ConcurrentHashMap<>(); 22 | 23 | public AnomalyDetectionResult(IndicatorInfo indicatorInfo) { 24 | this.indicatorInfo = indicatorInfo; 25 | } 26 | 27 | public AnomalyDetectionResult() { 28 | } 29 | 30 | public void addIndicatorEvaluateInfo(AnomalyDictType anomalyType, IndicatorEvaluateInfo evaluateInfo){ 31 | if (evaluateInfo == null) { 32 | return; 33 | } 34 | this.indicatorEvaluateMap.compute(anomalyType, (k,v) -> v == null ? new ArrayList<>() : v).add(evaluateInfo); 35 | } 36 | 37 | public ArrayList getIndicatorEvaluateInfo(AnomalyDictType anomalyType){ 38 | return this.indicatorEvaluateMap.get(anomalyType); 39 | } 40 | 41 | public Map> getIndicatorEvaluateMap() { 42 | return indicatorEvaluateMap; 43 | } 44 | 45 | public void setIndicatorEvaluateMap(Map> indicatorEvaluateMap) { 46 | this.indicatorEvaluateMap = indicatorEvaluateMap; 47 | } 48 | 49 | public IndicatorInfo getIndicatorInfo() { 50 | return indicatorInfo; 51 | } 52 | 53 | public void setIndicatorInfo(IndicatorInfo indicatorInfo) { 54 | this.indicatorInfo = indicatorInfo; 55 | } 56 | 57 | public boolean hasAnomaly(){ 58 | return indicatorEvaluateMap != null && indicatorEvaluateMap.size() == 0; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/AnomalyIndicatorSeries.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 4 | 5 | public class AnomalyIndicatorSeries { 6 | /** 7 | * anomaly influence 8 | */ 9 | private AnomalyDictType anomalyInfluence; 10 | 11 | /** 12 | * indicator series 13 | */ 14 | private IndicatorSeries indicatorSeries; 15 | 16 | public AnomalyIndicatorSeries() { 17 | } 18 | 19 | public AnomalyIndicatorSeries(IndicatorSeries indicatorSeries) { 20 | this.indicatorSeries = indicatorSeries; 21 | } 22 | 23 | public AnomalyIndicatorSeries(AnomalyDictType anomalyInfluence, IndicatorSeries indicatorSeries) { 24 | this.anomalyInfluence = anomalyInfluence; 25 | this.indicatorSeries = indicatorSeries; 26 | } 27 | 28 | public AnomalyDictType getAnomalyInfluence() { 29 | return anomalyInfluence; 30 | } 31 | 32 | public void setAnomalyInfluence(AnomalyDictType anomalyInfluence) { 33 | this.anomalyInfluence = anomalyInfluence; 34 | } 35 | 36 | public IndicatorSeries getIndicatorSeries() { 37 | return indicatorSeries; 38 | } 39 | 40 | public void setIndicatorSeries(IndicatorSeries indicatorSeries) { 41 | this.indicatorSeries = indicatorSeries; 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "["+anomalyInfluence + ":" + indicatorSeries.getTime()+", "+indicatorSeries.getValue()+", "+indicatorSeries.getLogicalIndex()+"]"; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/IndicatorEvaluateInfo.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 4 | 5 | import java.io.Serializable; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public class IndicatorEvaluateInfo implements Serializable { 10 | private boolean hasAnomaly; 11 | /** 12 | * model name 13 | */ 14 | private AnomalyDictType anomalyDetectionModel; 15 | 16 | /** 17 | * anomaly type 18 | */ 19 | private AnomalyDictType anomalyType; 20 | 21 | /** 22 | * anomaly trend 23 | */ 24 | private AnomalyDictType anomalyTrend; 25 | /** 26 | * anomaly indicator point 27 | */ 28 | private List anomalySeriesList; 29 | /** 30 | * Normal range:min 31 | */ 32 | private Double normalRangeMin; 33 | /** 34 | * Normal range:max 35 | */ 36 | private Double normalRangeMax; 37 | 38 | public IndicatorEvaluateInfo(AnomalyDictType anomalyDetectionModel, AnomalyDictType anomalyType, boolean hasAnomaly) { 39 | if (hasAnomaly) { 40 | this.anomalySeriesList = new ArrayList<>(); 41 | } 42 | this.anomalyType = anomalyType; 43 | this.anomalyDetectionModel = anomalyDetectionModel; 44 | this.hasAnomaly = hasAnomaly; 45 | } 46 | 47 | public void add(AnomalyIndicatorSeries series) { 48 | if(getAnomalySeriesList() == null){ 49 | this.anomalySeriesList = new ArrayList<>(); 50 | } 51 | getAnomalySeriesList().add(series); 52 | } 53 | 54 | public boolean isHasAnomaly() { 55 | return hasAnomaly; 56 | } 57 | 58 | public void setHasAnomaly(boolean hasAnomaly) { 59 | this.hasAnomaly = hasAnomaly; 60 | } 61 | 62 | public List getAnomalySeriesList() { 63 | return anomalySeriesList; 64 | } 65 | 66 | public void setAnomalySeriesList(List anomalySeriesList) { 67 | this.anomalySeriesList = anomalySeriesList; 68 | } 69 | 70 | public Double getNormalRangeMin() { 71 | return normalRangeMin; 72 | } 73 | 74 | public void setNormalRangeMin(Double normalRangeMin) { 75 | this.normalRangeMin = normalRangeMin; 76 | } 77 | 78 | public Double getNormalRangeMax() { 79 | return normalRangeMax; 80 | } 81 | 82 | public void setNormalRangeMax(Double normalRangeMax) { 83 | this.normalRangeMax = normalRangeMax; 84 | } 85 | 86 | public AnomalyDictType getAnomalyDetectionModel() { 87 | return anomalyDetectionModel; 88 | } 89 | 90 | public void setAnomalyDetectionModel(AnomalyDictType anomalyDetectionModel) { 91 | this.anomalyDetectionModel = anomalyDetectionModel; 92 | } 93 | 94 | public AnomalyDictType getAnomalyType() { 95 | return anomalyType; 96 | } 97 | 98 | public void setAnomalyType(AnomalyDictType anomalyType) { 99 | this.anomalyType = anomalyType; 100 | } 101 | 102 | public AnomalyDictType getAnomalyTrend() { 103 | return anomalyTrend; 104 | } 105 | 106 | public void setAnomalyTrend(AnomalyDictType anomalyTrend) { 107 | this.anomalyTrend = anomalyTrend; 108 | } 109 | 110 | @Override 111 | public String toString() { 112 | return "IndicatorEvaluateInfo{" + 113 | "anomalyDetectionModel='" + anomalyDetectionModel + '\'' + 114 | ", anomalyType=" + anomalyType + 115 | ", hasAnomaly=" + hasAnomaly + 116 | ", anomalySeriesList=" + anomalySeriesList + 117 | ", anomalyTrend=" + anomalyTrend + 118 | ", range=[" + normalRangeMin + ", " + normalRangeMax + "]" + 119 | '}'; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/IndicatorInfo.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | public class IndicatorInfo implements Serializable { 7 | 8 | /** 9 | * indicator en 10 | */ 11 | private String indicator; 12 | /** 13 | * indicator name 14 | */ 15 | private String indicatorName; 16 | /** 17 | * indicator series 18 | */ 19 | private List indicatorSeries; 20 | 21 | public IndicatorInfo() { 22 | } 23 | 24 | public IndicatorInfo(String indicator, String indicatorName, List indicatorSeries) { 25 | this.indicator = indicator; 26 | this.indicatorName = indicatorName; 27 | this.indicatorSeries = indicatorSeries; 28 | } 29 | 30 | public String getIndicator() { 31 | return indicator; 32 | } 33 | 34 | public void setIndicator(String indicator) { 35 | this.indicator = indicator; 36 | } 37 | 38 | public String getIndicatorName() { 39 | return indicatorName; 40 | } 41 | 42 | public void setIndicatorName(String indicatorName) { 43 | this.indicatorName = indicatorName; 44 | } 45 | 46 | public List getIndicatorSeries() { 47 | return indicatorSeries; 48 | } 49 | 50 | public void setIndicatorSeries(List indicatorSeries) { 51 | this.indicatorSeries = indicatorSeries; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/IndicatorSeries.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import java.io.Serializable; 4 | 5 | 6 | public class IndicatorSeries implements Serializable { 7 | /** indicator time, you can set other number for Timing Significance Values */ 8 | private final long time; 9 | /** indicator value */ 10 | private final double value; 11 | /** business logical index,facilitate business tracking. for example: ID */ 12 | private final String logicalIndex; 13 | 14 | public IndicatorSeries(long time, double value, String logicalIndex) { 15 | this.time = time; 16 | this.value = value; 17 | this.logicalIndex = logicalIndex; 18 | } 19 | 20 | public long getTime() { 21 | return time; 22 | } 23 | 24 | public double getValue() { 25 | return value; 26 | } 27 | 28 | public String getLogicalIndex() { 29 | return logicalIndex; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "["+time+", "+value+", "+logicalIndex+"]"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/ThresholdRule.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.algorithmtools.ad4j.enumtype.CompareType; 4 | import org.algorithmtools.ad4j.enumtype.ThresholdType; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * Threshold Rule: Indicator compareType (factor * thresholdType) 10 | */ 11 | public class ThresholdRule implements ThresholdRuleBase{ 12 | 13 | ThresholdType thresholdType; 14 | 15 | CompareType compareType; 16 | 17 | Double factor; 18 | 19 | public ThresholdRule(ThresholdType thresholdType, CompareType compareType, Double factor) { 20 | this.thresholdType = thresholdType; 21 | this.compareType = compareType; 22 | this.factor = factor; 23 | } 24 | 25 | public ThresholdType getThresholdType() { 26 | return thresholdType; 27 | } 28 | 29 | public CompareType getCompareType() { 30 | return compareType; 31 | } 32 | 33 | public Double getFactor() { 34 | return factor; 35 | } 36 | 37 | @Override 38 | public List getThresholdRules() { 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/ThresholdRuleBase.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | public interface ThresholdRuleBase extends Serializable { 7 | 8 | List getThresholdRules(); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/pojo/ThresholdRuleGroup.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.pojo; 2 | 3 | import org.algorithmtools.ad4j.enumtype.LogicType; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Threshold Rule Group 10 | */ 11 | public class ThresholdRuleGroup implements ThresholdRuleBase { 12 | 13 | List thresholdRules; 14 | 15 | LogicType logicType; 16 | 17 | public ThresholdRuleGroup(List thresholdRules, LogicType logicType) { 18 | this.thresholdRules = thresholdRules; 19 | this.logicType = logicType; 20 | } 21 | 22 | public ThresholdRuleGroup(LogicType logicType) { 23 | this.logicType = logicType; 24 | } 25 | 26 | public void addRules(ThresholdRuleBase rule) { 27 | if (this.thresholdRules == null) { 28 | thresholdRules = new ArrayList<>(); 29 | } 30 | thresholdRules.add(rule); 31 | } 32 | 33 | @Override 34 | public List getThresholdRules() { 35 | return thresholdRules; 36 | } 37 | 38 | public LogicType getLogicType() { 39 | return logicType; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/utils/BandwidthUtil.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.utils; 2 | 3 | import java.util.Arrays; 4 | 5 | public class BandwidthUtil { 6 | 7 | /** 8 | * calculate bandwidth with Silverman's Rule of Thumb 9 | * @param data data 10 | * @return bandwidth h 11 | */ 12 | public static double calculateBandwidth(double[] data) { 13 | int n = data.length; 14 | 15 | // calculate std 16 | double mean = Arrays.stream(data).average().orElse(0.0); 17 | double variance = Arrays.stream(data) 18 | .map(x -> (x - mean) * (x - mean)) 19 | .sum() / n; 20 | double stdDev = Math.sqrt(variance); 21 | 22 | // calculate IQR 23 | Arrays.sort(data); 24 | double q1 = getPercentile(data, 25); // 25% 25 | double q3 = getPercentile(data, 75); // 75% 26 | double iqr = q3 - q1; // IQR = Q3 - Q1 27 | 28 | // calculate h 29 | double factor = Math.min(stdDev, iqr / 1.34); 30 | return 0.9 * factor * Math.pow(n, -1.0 / 5.0); 31 | } 32 | 33 | /** 34 | * Calculate array percentile value 35 | * @param data after sorted array 36 | * @param percentile percentile(0-100) 37 | * @return percentile value 38 | */ 39 | private static double getPercentile(double[] data, double percentile) { 40 | double index = (percentile / 100.0) * (data.length - 1); 41 | int lower = (int) Math.floor(index); 42 | int upper = (int) Math.ceil(index); 43 | if (lower == upper) { 44 | return data[lower]; 45 | } 46 | double weight = index - lower; 47 | return data[lower] * (1 - weight) + data[upper] * weight; 48 | } 49 | 50 | public static void main(String[] args) { 51 | // double[] data = {1.2, 1.5, 2.0, 2.1, 3.3, 3.9, 4.8, 10.0, 10.5, 12.0}; 52 | // double[] data = {10.0, 12.0, 85.0, 70.0, 100.0, 14.0, 14.0, 12.0, 40.0, 20.0}; 53 | // double[] data = {100, 102, 101, 180, 105, 108, 182, 110, 110,182,121}; 54 | double[] data = {45.29, 30.85, 40.23, 15.57, 13.14, 32.53, 44.34, 33.92, 25.31, 31.12, 33.23, 40.65, 32.88, 31.14}; 55 | double bandwidth = calculateBandwidth(data); 56 | System.out.printf("Optimal Bandwidth: %.4f%n", bandwidth); 57 | } 58 | 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/utils/CollectionUtil.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.utils; 2 | 3 | import java.util.List; 4 | 5 | public class CollectionUtil { 6 | 7 | public static boolean isEmpty(List list){ 8 | return list == null || list.isEmpty(); 9 | } 10 | 11 | public static boolean isNotEmpty(List list){ 12 | return !isEmpty(list); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/utils/IndicatorCalculateUtil.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.utils; 2 | 3 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 4 | import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; 5 | 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.stream.Collectors; 9 | 10 | public class IndicatorCalculateUtil { 11 | 12 | /** 13 | * calculate define range 14 | * @param data indicator series 15 | * @param iqrMultiplier iqrMultiplier 16 | * @param lowerQuantile lowerQuantile 17 | * @param upperQuantile upperQuantile 18 | * @return [lowerBound, upperBound] 19 | */ 20 | public static double[] quantileBound(List data, double iqrMultiplier, double lowerQuantile, double upperQuantile){ 21 | // sort 22 | List sortList = sortAsc(data); 23 | 24 | // calculate quantile value 25 | double Q1 = quantile(sortList, lowerQuantile); 26 | double Q3 = quantile(sortList, upperQuantile); 27 | 28 | // calculate IQR 29 | double IQR = Q3 - Q1; 30 | 31 | // calculate bound 32 | double lowerBound = Q1 - iqrMultiplier * IQR; 33 | double upperBound = Q3 + iqrMultiplier * IQR; 34 | return new double[]{lowerBound, upperBound}; 35 | } 36 | 37 | /** 38 | * interquartile range 39 | * @param data indicator series 40 | * @return [lowerBound, upperBound] 41 | */ 42 | public static double[] quantileIQR(List data){ 43 | return quantileBound(data, 1.5, 0.25, 0.75); 44 | } 45 | 46 | public static List excludeOutlier(List data){ 47 | double[] bound = quantileIQR(data); 48 | double lowerBound = bound[0]; 49 | double upperBound = bound[1]; 50 | 51 | // find normal series 52 | return data.stream().filter(v -> v.getValue() <= upperBound && v.getValue() >= lowerBound).collect(Collectors.toList()); 53 | } 54 | 55 | public static double quantile(List sortedData, double quantile) { 56 | int n = sortedData.size(); 57 | double index = quantile * (n - 1); 58 | int lowerIndex = (int) Math.floor(index); 59 | int upperIndex = (int) Math.ceil(index); 60 | 61 | if (lowerIndex == upperIndex) { 62 | return sortedData.get(lowerIndex).getValue(); 63 | } 64 | 65 | double weight = index - lowerIndex; 66 | return (1 - weight) * sortedData.get(lowerIndex).getValue() + weight * sortedData.get(upperIndex).getValue(); 67 | } 68 | 69 | public static DescriptiveStatistics initStatistic(DescriptiveStatistics stats, List dataList, List excludeIndexList) { 70 | if (Objects.isNull(stats)) { 71 | stats = new DescriptiveStatistics(); 72 | } else { 73 | stats.clear(); 74 | } 75 | 76 | for (int i = 0; i < dataList.size(); i++) { 77 | if (excludeIndexList == null || !excludeIndexList.contains(i)) { 78 | stats.addValue(dataList.get(i).getValue()); 79 | } 80 | } 81 | return stats; 82 | } 83 | 84 | public static List sortAsc(List data){ 85 | return data.stream().sorted(new IndicatorSeriesComparator()).collect(Collectors.toList()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/utils/IndicatorSeriesComparator.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.utils; 2 | 3 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 4 | 5 | import java.util.Comparator; 6 | 7 | public class IndicatorSeriesComparator implements Comparator { 8 | @Override 9 | public int compare(IndicatorSeries o1, IndicatorSeries o2) { 10 | if (o1.getValue() > o2.getValue()) { 11 | return 1; 12 | } else if (o1.getValue() < o2.getValue()) { 13 | return -1; 14 | } else { 15 | return Long.compare(o1.getTime(), o2.getTime()); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/algorithmtools/ad4j/utils/IndicatorSeriesUtil.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.utils; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.algorithmtools.ad4j.enumtype.AnomalyDictType; 5 | import org.algorithmtools.ad4j.pojo.AnomalyDetectionResult; 6 | import org.algorithmtools.ad4j.pojo.IndicatorEvaluateInfo; 7 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | public class IndicatorSeriesUtil { 14 | 15 | public static List transferFromArray(double[] array){ 16 | List list = new ArrayList(); 17 | for (int i = 0; i < array.length; i++) { 18 | list.add(i, new IndicatorSeries(i, array[i], String.valueOf(i))); 19 | } 20 | return list; 21 | } 22 | 23 | public static double[] transferToArray(List series){ 24 | double[] resultArray = new double[series.size()]; 25 | for (int i = 0; i < series.size(); i++) { 26 | resultArray[i] = series.get(i).getValue(); 27 | } 28 | return resultArray; 29 | } 30 | 31 | public static void print(IndicatorEvaluateInfo evaluateInfo){ 32 | System.out.println(evaluateInfo); 33 | } 34 | 35 | public static void print(AnomalyDetectionResult result){ 36 | System.out.println("==============Anomaly Detection Result============="); 37 | StringBuilder printString = new StringBuilder(); 38 | printString.append("1.Anomaly detection original data:").append(result.getIndicatorInfo().getIndicatorSeries()); 39 | printString.append("\n"); 40 | printString.append("2.Overview: has ").append(result.getIndicatorEvaluateMap().size()).append(" types anomaly detected."); 41 | int i = 1; 42 | for (Map.Entry> entry : result.getIndicatorEvaluateMap().entrySet()) { 43 | printString.append("\n"); 44 | printString.append("3.").append(i).append(".[Anomaly: ").append(entry.getKey().getEnName()).append("] ").append(JSONObject.toJSONString(entry.getValue())); 45 | i++; 46 | } 47 | 48 | System.out.println(printString); 49 | 50 | System.out.println("==============Anomaly Detection Result End========="); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/ad4j/model/ADEngineTest.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.algorithmtools.ad4j.engine.AnomalyDetectionEngine; 5 | import org.algorithmtools.ad4j.pojo.*; 6 | import org.algorithmtools.ad4j.utils.IndicatorSeriesUtil; 7 | import org.algorithmtools.chart.JFreeChartUtil; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | 12 | import java.util.List; 13 | import java.util.Scanner; 14 | 15 | public class ADEngineTest { 16 | 17 | public AnomalyDetectionLog log; 18 | 19 | @Before 20 | public void before(){ 21 | log = new AnomalyDetectionLog(); 22 | } 23 | 24 | @Test 25 | public void testEngin(){ 26 | double[] data = new double[]{10.0, 12.0, 12.5, 133.0, 13.0, 10.5, 100.0, 14.0, 15.0, 14.5, 15.5}; 27 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 28 | JFreeChartUtil.drawLineChart("TestAD_Engin", indicatorSeries); 29 | 30 | IndicatorInfo info = new IndicatorInfo("Test", "Test-name", indicatorSeries); 31 | AnomalyDetectionEngine engine = new AnomalyDetectionEngine(); 32 | AnomalyDetectionResult detect = engine.detect(info); 33 | IndicatorSeriesUtil.print(detect); 34 | } 35 | 36 | @Test 37 | public void testEnginByConfig(){ 38 | double[] data = new double[]{11.5, 12, 10, 36, 13.0, 10.5, 5, 14.0, 15.0, 14.5, 15.5}; 39 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 40 | JFreeChartUtil.drawLineChart("TestAD_Engin", indicatorSeries); 41 | 42 | IndicatorInfo info = new IndicatorInfo("Test", "Test-name", indicatorSeries); 43 | 44 | JSONObject propertiesJson = new JSONObject(); 45 | propertiesJson.put("adm.zscore.use", false); 46 | propertiesJson.put("adm.gesd.use", false); 47 | propertiesJson.put("adm.gesd.alpha", 0.1); 48 | propertiesJson.put("adm.quantile.use", false); 49 | propertiesJson.put("adm.quantile.iqr.multiplier", 2); 50 | propertiesJson.put("adm.2nd_derivation_mbp.use", true); 51 | propertiesJson.put("adm.2nd_derivation_mbp.threshold_factor", "0.1s"); 52 | propertiesJson.put("adm.mannkendall.use", false); 53 | propertiesJson.put("adm.mannkendall.criticalz", "0.01s"); 54 | propertiesJson.put("adm.threshold_rule.use", false); 55 | 56 | AnomalyDetectionContext anomalyDetectionContext = AnomalyDetectionContext.create(propertiesJson); 57 | AnomalyDetectionEngine engine = new AnomalyDetectionEngine(anomalyDetectionContext); 58 | 59 | AnomalyDetectionResult detect = engine.detect(info); 60 | IndicatorSeriesUtil.print(detect); 61 | } 62 | 63 | @After 64 | public void afterSleep() throws InterruptedException { 65 | System.out.println("click Enter to close window..."); 66 | Scanner scanner = new Scanner(System.in); 67 | scanner.nextLine(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/ad4j/model/ADMTest.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model; 2 | 3 | import com.alibaba.fastjson.JSONArray; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.algorithmtools.ad4j.config.ADMConfigs; 6 | import org.algorithmtools.ad4j.enumtype.CompareType; 7 | import org.algorithmtools.ad4j.enumtype.LogicType; 8 | import org.algorithmtools.ad4j.enumtype.ThresholdType; 9 | import org.algorithmtools.ad4j.model.adm.*; 10 | import org.algorithmtools.ad4j.pojo.*; 11 | import org.algorithmtools.ad4j.utils.IndicatorSeriesUtil; 12 | import org.algorithmtools.chart.JFreeChartUtil; 13 | import org.junit.After; 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Scanner; 20 | 21 | public class ADMTest { 22 | 23 | public AnomalyDetectionLog log; 24 | 25 | @Before 26 | public void before(){ 27 | log = new AnomalyDetectionLog(); 28 | } 29 | 30 | @Test 31 | public void testADM_2ndDerivationMBP(){ 32 | double[] data = {10.0, 12.0, 85.0, 70, 100.0, 14.0, 14.0, 12.0, 40, 20, 20}; 33 | // List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 34 | List indicatorSeries = parseData("[{\"logicalIndex\":\"0\",\"time\":0,\"value\":1104.0},{\"logicalIndex\":\"1\",\"time\":1,\"value\":976.0},{\"logicalIndex\":\"2\",\"time\":2,\"value\":949.0},{\"logicalIndex\":\"3\",\"time\":3,\"value\":895.0},{\"logicalIndex\":\"4\",\"time\":4,\"value\":810.0},{\"logicalIndex\":\"5\",\"time\":5,\"value\":975.0},{\"logicalIndex\":\"6\",\"time\":6,\"value\":1152.0},{\"logicalIndex\":\"7\",\"time\":7,\"value\":818.0},{\"logicalIndex\":\"8\",\"time\":8,\"value\":766.0},{\"logicalIndex\":\"9\",\"time\":9,\"value\":502.0},{\"logicalIndex\":\"10\",\"time\":10,\"value\":396.0},{\"logicalIndex\":\"11\",\"time\":11,\"value\":468.0},{\"logicalIndex\":\"12\",\"time\":12,\"value\":592.0},{\"logicalIndex\":\"13\",\"time\":13,\"value\":769.0}]"); 35 | JFreeChartUtil.drawLineChart("testADM_2ndDerivationMBP", indicatorSeries); 36 | 37 | AbstractADM model = new ADM_2ndDerivationMBP(); 38 | model.init(AnomalyDetectionContext.createDefault()); 39 | model.checkCompatibility(indicatorSeries, log); 40 | 41 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 42 | IndicatorSeriesUtil.print(evaluate); 43 | } 44 | 45 | @Test 46 | public void testADM_GESD(){ 47 | double[] data = new double[]{10.0,12.0,12.0,13.0,12.0,11.0,50.0}; 48 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 49 | AbstractADM model = new ADM_GESD(); 50 | model.init(AnomalyDetectionContext.createDefault()); 51 | model.checkCompatibility(indicatorSeries, log); 52 | 53 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 54 | IndicatorSeriesUtil.print(evaluate); 55 | } 56 | 57 | @Test 58 | public void testADM_MannKendall(){ 59 | double[] data = new double[]{10.0, 12.0, 12.5, 13.0, 55.0, 10.5, 14.0, 15.0, 14.5, 16.0}; 60 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 61 | AbstractADM model = new ADM_MannKendall(); 62 | model.init(AnomalyDetectionContext.createDefault()); 63 | model.checkCompatibility(indicatorSeries, log); 64 | 65 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 66 | IndicatorSeriesUtil.print(evaluate); 67 | } 68 | 69 | @Test 70 | public void testADM_Quantile(){ 71 | double[] data = new double[]{10.0, 12.0, 12.5, 133.0, 13.0, 10.5, 100.0, 14.0, 15.0, 14.5, 15.5}; 72 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 73 | JFreeChartUtil.drawLineChart("TestADM_Quantile_Line", indicatorSeries); 74 | JFreeChartUtil.drawScatterChart("TestADM_Quantile_Scatter", indicatorSeries); 75 | 76 | AbstractADM model = new ADM_Quantile(); 77 | model.init(AnomalyDetectionContext.createDefault()); 78 | model.checkCompatibility(indicatorSeries, log); 79 | 80 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 81 | IndicatorSeriesUtil.print(evaluate); 82 | } 83 | 84 | @Test 85 | public void testADM_ZScore(){ 86 | double[] data = new double[]{10.0, 12.0, 12.5, 133.0, 13.0, 10.5, 100.0, 14.0, 15.0, 14.5, 15.5}; 87 | data = new double[]{ 88 | 1.26 89 | ,1.10 90 | ,1.54 91 | ,2.58 92 | ,3.48 93 | ,1.64 94 | ,1.74 95 | ,1.36 96 | ,2.53 97 | ,2.47 98 | ,1.56 99 | ,0.91 100 | ,2.00 101 | }; 102 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 103 | AbstractADM model = new ADM_ZScore(); 104 | model.init(AnomalyDetectionContext.createDefault()); 105 | model.checkCompatibility(indicatorSeries, log); 106 | 107 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 108 | IndicatorSeriesUtil.print(evaluate); 109 | } 110 | 111 | @Test 112 | public void testADM_ThresholdRule(){ 113 | double[] data = new double[]{1.0,2.0,3.0,4.0}; 114 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 115 | AbstractADM model = new ADM_ThresholdRule(); 116 | AnomalyDetectionContext adContext = AnomalyDetectionContext.createDefault(); 117 | adContext.putConfig(ADMConfigs.ADM_THRESHOLD_RULE_USE, true); 118 | 119 | ThresholdRuleGroup ruleGroupL2 = new ThresholdRuleGroup(LogicType.AND); 120 | ThresholdRuleBase ruleBaseL2_1 = new ThresholdRule(ThresholdType.MIN, CompareType.GREATER, 1.0); 121 | ThresholdRuleBase ruleBaseL2_2 = new ThresholdRule(ThresholdType.MAX, CompareType.LESS, 1.0); 122 | ruleGroupL2.addRules(ruleBaseL2_1); 123 | ruleGroupL2.addRules(ruleBaseL2_2); 124 | ThresholdRuleBase ruleBaseL1_1 = new ThresholdRule(ThresholdType.CONSTANT, CompareType.GREATER_OR_EQ, 5.0); 125 | ThresholdRuleBase ruleBaseL1_2 = new ThresholdRule(ThresholdType.CONSTANT, CompareType.LESS_OR_EQ, 1.0); 126 | ThresholdRuleGroup ruleGroupL1 = new ThresholdRuleGroup(LogicType.OR); 127 | ruleGroupL1.addRules(ruleBaseL1_1); 128 | ruleGroupL1.addRules(ruleBaseL1_2); 129 | ruleGroupL1.addRules(ruleGroupL2); 130 | adContext.putConfig(ADMConfigs.ADM_THRESHOLD_RULE_SET, ruleGroupL1); 131 | model.init(adContext); 132 | model.checkCompatibility(indicatorSeries, log); 133 | 134 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 135 | IndicatorSeriesUtil.print(evaluate); 136 | } 137 | 138 | @Test 139 | public void testADM_ThresholdRule2(){ 140 | double[] data = new double[]{1.0,2.0,3.0,4.0}; 141 | List indicatorSeries = IndicatorSeriesUtil.transferFromArray(data); 142 | AbstractADM model = new ADM_ThresholdRule(); 143 | 144 | String propertiesJsonStr = "{\"adm.quantile.use\":false,\"adm.threshold_rule.use\":true,\"adm.threshold_rule.set\":{\"logicType\":\"or\",\"ruleGroup\":[{\"factor\":5.0,\"thresholdType\":\"constant\",\"compareType\":\">=\"},{\"factor\":1.0,\"thresholdType\":\"constant\",\"compareType\":\"<=\"}]}}"; 145 | JSONObject propertiesJson = JSONObject.parseObject(propertiesJsonStr); 146 | AnomalyDetectionContext adContext = AnomalyDetectionContext.create(propertiesJson); 147 | 148 | model.init(adContext); 149 | model.checkCompatibility(indicatorSeries, log); 150 | 151 | IndicatorEvaluateInfo evaluate = model.evaluate(indicatorSeries, log); 152 | IndicatorSeriesUtil.print(evaluate); 153 | } 154 | 155 | public List parseData(String s){ 156 | s = s != null ? s : 157 | "[{\"logicalIndex\":\"0\",\"time\":0,\"value\":45.29},{\"logicalIndex\":\"1\",\"time\":1,\"value\":30.85},{\"logicalIndex\":\"2\",\"time\":2,\"value\":40.23},{\"logicalIndex\":\"3\",\"time\":3,\"value\":15.57},{\"logicalIndex\":\"4\",\"time\":4,\"value\":13.14},{\"logicalIndex\":\"5\",\"time\":5,\"value\":32.53},{\"logicalIndex\":\"6\",\"time\":6,\"value\":44.34},{\"logicalIndex\":\"7\",\"time\":7,\"value\":33.92},{\"logicalIndex\":\"8\",\"time\":8,\"value\":25.31},{\"logicalIndex\":\"9\",\"time\":9,\"value\":31.12},{\"logicalIndex\":\"10\",\"time\":10,\"value\":33.23},{\"logicalIndex\":\"11\",\"time\":11,\"value\":40.65},{\"logicalIndex\":\"12\",\"time\":12,\"value\":32.88},{\"logicalIndex\":\"13\",\"time\":13,\"value\":31.14}]"; 158 | JSONArray jsonArray = JSONArray.parseArray(s); 159 | List indicatorSeries = new ArrayList<>(); 160 | for (Object o : jsonArray) { 161 | JSONObject json = (JSONObject) o; 162 | indicatorSeries.add(new IndicatorSeries(json.getLong("time"), json.getDoubleValue("value"), json.getString("logicalIndex"))); 163 | } 164 | return indicatorSeries; 165 | } 166 | 167 | @After 168 | public void afterSleep() throws InterruptedException { 169 | System.out.println("click Enter to close window..."); 170 | Scanner scanner = new Scanner(System.in); 171 | scanner.nextLine(); 172 | } 173 | 174 | } 175 | -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/ad4j/model/VolatilityAnalysisTest.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.ad4j.model; 2 | 3 | import org.algorithmtools.ad4j.utils.BandwidthUtil; 4 | import org.algorithmtools.ad4j.utils.IndicatorSeriesUtil; 5 | import org.algorithmtools.chart.JFreeChartUtil; 6 | import smile.stat.distribution.KernelDensity; 7 | 8 | import java.util.*; 9 | 10 | /** 11 | * Test Volatility Anomaly 12 | */ 13 | public class VolatilityAnalysisTest { 14 | 15 | public static void main(String[] args) { 16 | // double[] data = {10.0, 12.0, 85.0, 70.0, 100.0, 14.0, 14.0, 12.0, 40.0, 20.0}; 17 | // double[] data = {45.29, 30.85, 40.23, 15.57, 13.14, 32.53, 44.34, 33.92, 25.31, 31.12, 33.23, 40.65, 32.88, 31.14}; 18 | double[] data = {1104.0, 976.0, 949.0, 895.0, 810.0, 975.0, 1152.0, 818.0, 766.0, 502.0, 396.0, 468.0, 592.0, 769.0}; 19 | JFreeChartUtil.drawLineChart("data", IndicatorSeriesUtil.transferFromArray(data)); 20 | 21 | // volatility 22 | double[] volatility = calculateVolatility(data); 23 | JFreeChartUtil.drawLineChart("volatility", IndicatorSeriesUtil.transferFromArray(volatility)); 24 | 25 | // volatility to distribution points 26 | double[][] distributionPoints = transferToDistributionPoints(calculateVolatility(data), 100); 27 | double[] x = distributionPoints[0]; 28 | double[] y = distributionPoints[1]; 29 | JFreeChartUtil.drawLineChart("KDE", x, y); 30 | 31 | double[] secondDerivatives = calculateSecondDerivatives(x, y); 32 | JFreeChartUtil.drawLineChart("secondDerivative", x, secondDerivatives); 33 | 34 | // find MBP 35 | int[] inflectionPoints = findInflectionPoints(secondDerivatives, x, y); 36 | double minBound = x[inflectionPoints[0]]; 37 | double maxBound = x[inflectionPoints[1]]; 38 | System.out.println("0-axis left inflection point threshold:" + minBound + "\t 0-axis right inflection point threshold:" + maxBound); 39 | 40 | // check anomaly 41 | checkAnomalies(data, volatility, minBound, maxBound); 42 | } 43 | 44 | public static double[] calculateVolatility(double[] data) { 45 | double[] volatility = new double[data.length]; 46 | volatility[0] = 0; 47 | for (int i = 1; i < data.length; i++) { 48 | volatility[i] = (data[i] - data[i - 1]) / data[i - 1]; 49 | } 50 | return volatility; 51 | } 52 | 53 | public static double[][] transferToDistributionPoints(double[] data, int points) { 54 | KernelDensity volatilityDistribution = new KernelDensity(data, BandwidthUtil.calculateBandwidth(data) * 1); 55 | 56 | double min = Arrays.stream(data).min().orElse(0.0); 57 | double max = Arrays.stream(data).max().orElse(1.0); 58 | double step = (max - min) / points; 59 | 60 | double[] x = new double[points]; 61 | double[] y = new double[points]; 62 | for (int i = 0; i < points; i++) { 63 | x[i] = min + i * step; 64 | y[i] = volatilityDistribution.p(x[i]); // KDE estimate 65 | } 66 | 67 | return new double[][]{x, y}; 68 | } 69 | 70 | public static double[] calculateSecondDerivatives(double[] x, double[] y) { 71 | int n = x.length; 72 | if (n < 3) { 73 | throw new IllegalArgumentException("At least three points are required to compute second derivatives."); 74 | } 75 | 76 | double h; 77 | double[] secondDerivatives = new double[n]; 78 | // Compute second derivatives 79 | for (int i = 0; i < n; i++) { 80 | h = x[1] - x[0]; 81 | if (i == 0) { 82 | // Left boundary 83 | secondDerivatives[i] = (y[i + 2] - 2 * y[i + 1] + y[i]) / (h * h); 84 | } else if (i == n - 1) { 85 | // Right boundary 86 | secondDerivatives[i] = (y[i] - 2 * y[i - 1] + y[i - 2]) / (h * h); 87 | } else { 88 | // Internal points 89 | secondDerivatives[i] = (y[i + 1] - 2 * y[i] + y[i - 1]) / (h * h); 90 | } 91 | } 92 | 93 | return secondDerivatives; 94 | } 95 | 96 | 97 | // find max bend point 98 | public static int[] findInflectionPoints(double[] secondDerivative, double[] distributionX, double[] distributionY) { 99 | int zeroLeftInflectionPointIndex = 0; 100 | double zeroLeftInflectionPointMaxDistance = -1; 101 | int zeroRightInflectionPointIndex = distributionX.length - 1; 102 | double zeroRightInflectionPointMaxDistance = -1; 103 | for (int i = 1; i < secondDerivative.length; i++) { 104 | // A change in sign could be an inflection point 105 | if (secondDerivative[i - 1] * secondDerivative[i] < 0) { 106 | double distance = Math.sqrt(Math.pow(distributionX[i], 2) + Math.pow(distributionY[i], 2)); 107 | if(distributionX[i] >= 0){ 108 | // zero right 109 | if(distance >= zeroRightInflectionPointMaxDistance){ 110 | zeroRightInflectionPointIndex = i; 111 | zeroRightInflectionPointMaxDistance = distance; 112 | } 113 | } else { 114 | // zero left 115 | if(distance >= zeroLeftInflectionPointMaxDistance){ 116 | zeroLeftInflectionPointIndex = i; 117 | zeroLeftInflectionPointMaxDistance = distance; 118 | } 119 | } 120 | } 121 | } 122 | 123 | return new int[]{zeroLeftInflectionPointIndex, zeroRightInflectionPointIndex}; 124 | } 125 | 126 | public static void checkAnomalies(double[] data, double[] volatility, double minBound, double maxBound) { 127 | Map anomalyList = new HashMap<>(); 128 | int continualMaxIndex = -1; 129 | int continualLastIndex = -1; 130 | for (int i = 0; i < volatility.length; i++) { 131 | if(volatility[i] > maxBound || volatility[i] < minBound){ 132 | // continual isotropic volatilises, select the largest 133 | if(continualLastIndex < 0){ 134 | continualMaxIndex = i; 135 | continualLastIndex = i; 136 | } else if(i - 1 == continualLastIndex && volatility[i] * volatility[continualLastIndex] > 0){ 137 | if(Math.abs(volatility[i]) > Math.abs(volatility[continualMaxIndex])){ 138 | continualMaxIndex = i; 139 | } 140 | continualLastIndex = i; 141 | } else { 142 | anomalyList.put(continualMaxIndex, data[continualMaxIndex]); 143 | continualLastIndex = i; 144 | continualMaxIndex = i; 145 | } 146 | } 147 | } 148 | if(continualMaxIndex >= 0){ 149 | anomalyList.put(continualMaxIndex, data[continualMaxIndex]); 150 | } 151 | 152 | anomalyList.forEach((k,v) -> System.out.println("anomaly:" + k + " --> " + v)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/chart/JFreeChartUtil.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.chart; 2 | 3 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 4 | import org.jfree.chart.ui.UIUtils; 5 | import org.jfree.data.xy.DefaultXYDataset; 6 | import org.jfree.data.xy.XYDataset; 7 | 8 | import javax.swing.*; 9 | import java.util.List; 10 | 11 | public class JFreeChartUtil { 12 | 13 | public static XYDataset transfer(DefaultXYDataset dataset, String series, double[] x, double[] y){ 14 | dataset = dataset == null ? new DefaultXYDataset() : dataset; 15 | dataset.addSeries(series, new double[][] {x, y}); 16 | return dataset; 17 | } 18 | 19 | public static XYDataset transfer(double[] x, double[] y){ 20 | return transfer(null, "data", x, y); 21 | } 22 | 23 | public static void drawLineChart(String title, double[] xData, double[] yData){ 24 | SwingUtilities.invokeLater(() -> { 25 | LineChart chart = new LineChart(JFreeChartUtil.transfer(xData, yData), title, "X", "Y", 800, 600); 26 | chart.pack(); 27 | UIUtils.centerFrameOnScreen(chart); 28 | chart.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 29 | chart.setVisible(true); 30 | }); 31 | } 32 | 33 | public static void drawLineChart(String title, List xData, List yData){ 34 | double[] x = new double[xData.size()]; 35 | double[] y = new double[yData.size()]; 36 | for (int i = 0; i < xData.size(); i++) { 37 | x[i] = xData.get(i); 38 | y[i] = yData.get(i); 39 | } 40 | 41 | drawLineChart(title, x, y); 42 | } 43 | 44 | public static void drawLineChart(String title, List data){ 45 | double[] x = new double[data.size()]; 46 | double[] y = new double[data.size()]; 47 | for (int i = 0; i < data.size(); i++) { 48 | x[i] = data.get(i).getTime(); 49 | y[i] = data.get(i).getValue(); 50 | } 51 | 52 | drawLineChart(title, x, y); 53 | } 54 | 55 | public static void drawScatterChart(String title, List data){ 56 | double[] x = new double[data.size()]; 57 | double[] y = new double[data.size()]; 58 | for (int i = 0; i < data.size(); i++) { 59 | x[i] = data.get(i).getTime(); 60 | y[i] = data.get(i).getValue(); 61 | } 62 | 63 | SwingUtilities.invokeLater(() -> { 64 | ScatterChart chart = new ScatterChart(JFreeChartUtil.transfer(x, y), title, "X", "Y", 800, 600); 65 | chart.pack(); 66 | UIUtils.centerFrameOnScreen(chart); 67 | chart.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); 68 | chart.setVisible(true); 69 | }); 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/chart/LineChart.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.chart; 2 | 3 | 4 | import org.jfree.chart.ChartFactory; 5 | import org.jfree.chart.ChartPanel; 6 | import org.jfree.chart.JFreeChart; 7 | import org.jfree.chart.axis.NumberAxis; 8 | import org.jfree.chart.plot.XYPlot; 9 | import org.jfree.chart.renderer.xy.XYItemRenderer; 10 | import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; 11 | import org.jfree.chart.ui.ApplicationFrame; 12 | import org.jfree.chart.ui.RectangleInsets; 13 | import org.jfree.chart.ui.UIUtils; 14 | import org.jfree.data.time.Month; 15 | import org.jfree.data.time.TimeSeries; 16 | import org.jfree.data.time.TimeSeriesCollection; 17 | import org.jfree.data.xy.XYDataset; 18 | 19 | import javax.swing.*; 20 | import java.awt.*; 21 | 22 | /** 23 | * An example of a time series chart create using JFreeChart. For the most 24 | * part, default settings are used, except that the renderer is modified to 25 | * show filled shapes (as well as lines) at each data point. 26 | */ 27 | public class LineChart extends ApplicationFrame { 28 | 29 | private static final long serialVersionUID = 1L; 30 | 31 | private final XYDataset xyDataset; 32 | 33 | private final String panelTitle; 34 | private final String xAxisLabel; 35 | private final String yAxisLabel; 36 | 37 | /** 38 | * A demonstration application showing how to create a simple time series 39 | * chart. This example uses monthly data. 40 | * 41 | * @param xyDataset xyDataset. 42 | * @param title the frame title. 43 | * @param xAxisLabel xAxisLabel. 44 | * @param yAxisLabel yAxisLabel. 45 | * @param width width. 46 | * @param height height. 47 | */ 48 | public LineChart(XYDataset xyDataset, String title, String xAxisLabel, String yAxisLabel, int width, int height) { 49 | super("Time series chart frame"); 50 | this.xyDataset = xyDataset; 51 | this.panelTitle = title; 52 | this.xAxisLabel = xAxisLabel; 53 | this.yAxisLabel = yAxisLabel; 54 | ChartPanel chartPanel = (ChartPanel) createDemoPanel(); 55 | chartPanel.setPreferredSize(new java.awt.Dimension(width, height)); 56 | setContentPane(chartPanel); 57 | } 58 | 59 | /** 60 | * Creates a panel for the demo (used by SuperDemo.java). 61 | * 62 | * @return A panel. 63 | */ 64 | public JPanel createDemoPanel() { 65 | JFreeChart chart = createChart(xyDataset); 66 | ChartPanel panel = new ChartPanel(chart, false); 67 | panel.setFillZoomRectangle(true); 68 | panel.setMouseWheelEnabled(true); 69 | return panel; 70 | } 71 | 72 | /** 73 | * Creates a chart. 74 | * 75 | * @param dataset a dataset. 76 | * 77 | * @return A chart. 78 | */ 79 | private JFreeChart createChart(XYDataset dataset) { 80 | 81 | JFreeChart chart = ChartFactory.createTimeSeriesChart( 82 | panelTitle, // title 83 | xAxisLabel, // x-axis label 84 | yAxisLabel, // y-axis label 85 | dataset); 86 | 87 | chart.setBackgroundPaint(Color.WHITE); 88 | 89 | XYPlot plot = (XYPlot) chart.getPlot(); 90 | plot.setBackgroundPaint(Color.LIGHT_GRAY); 91 | plot.setDomainGridlinePaint(Color.WHITE); 92 | plot.setRangeGridlinePaint(Color.WHITE); 93 | plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); 94 | plot.setDomainCrosshairVisible(true); 95 | plot.setRangeCrosshairVisible(true); 96 | 97 | XYItemRenderer r = plot.getRenderer(); 98 | if (r instanceof XYLineAndShapeRenderer) { 99 | XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r; 100 | renderer.setDefaultShapesVisible(true); 101 | renderer.setDefaultShapesFilled(true); 102 | renderer.setDrawSeriesLineAsPath(true); 103 | } 104 | 105 | NumberAxis xAxis = new NumberAxis("X"); 106 | plot.setDomainAxis(xAxis); 107 | // DateAxis axis = (DateAxis) plot.getDomainAxis(); 108 | // axis.setDateFormatOverride(new SimpleDateFormat("YYYY-MM-dd")); 109 | 110 | return chart; 111 | } 112 | 113 | /** 114 | * Creates a dataset, consisting of two series of monthly data. 115 | * 116 | * @return The dataset. 117 | */ 118 | private XYDataset createDataset() { 119 | 120 | TimeSeries s1 = new TimeSeries("L&G European Index Trust"); 121 | s1.add(new Month(2, 2001), 181.8); 122 | s1.add(new Month(3, 2001), 167.3); 123 | s1.add(new Month(4, 2001), 153.8); 124 | s1.add(new Month(5, 2001), 167.6); 125 | s1.add(new Month(6, 2001), 158.8); 126 | s1.add(new Month(7, 2001), 148.3); 127 | s1.add(new Month(8, 2001), 153.9); 128 | s1.add(new Month(9, 2001), 142.7); 129 | s1.add(new Month(10, 2001), 123.2); 130 | s1.add(new Month(11, 2001), 131.8); 131 | s1.add(new Month(12, 2001), 139.6); 132 | s1.add(new Month(1, 2002), 142.9); 133 | s1.add(new Month(2, 2002), 138.7); 134 | s1.add(new Month(3, 2002), 137.3); 135 | s1.add(new Month(4, 2002), 143.9); 136 | s1.add(new Month(5, 2002), 139.8); 137 | s1.add(new Month(6, 2002), 137.0); 138 | s1.add(new Month(7, 2002), 132.8); 139 | 140 | TimeSeries s2 = new TimeSeries("L&G UK Index Trust"); 141 | s2.add(new Month(2, 2001), 129.6); 142 | s2.add(new Month(3, 2001), 123.2); 143 | s2.add(new Month(4, 2001), 117.2); 144 | s2.add(new Month(5, 2001), 124.1); 145 | s2.add(new Month(6, 2001), 122.6); 146 | s2.add(new Month(7, 2001), 119.2); 147 | s2.add(new Month(8, 2001), 116.5); 148 | s2.add(new Month(9, 2001), 112.7); 149 | s2.add(new Month(10, 2001), 101.5); 150 | s2.add(new Month(11, 2001), 106.1); 151 | s2.add(new Month(12, 2001), 110.3); 152 | s2.add(new Month(1, 2002), 111.7); 153 | s2.add(new Month(2, 2002), 111.0); 154 | s2.add(new Month(3, 2002), 109.6); 155 | s2.add(new Month(4, 2002), 113.2); 156 | s2.add(new Month(5, 2002), 111.6); 157 | s2.add(new Month(6, 2002), 108.8); 158 | s2.add(new Month(7, 2002), 101.6); 159 | 160 | TimeSeriesCollection dataset = new TimeSeriesCollection(); 161 | dataset.addSeries(s1); 162 | dataset.addSeries(s2); 163 | 164 | return dataset; 165 | 166 | } 167 | 168 | /** 169 | * Starting point for the demonstration application. 170 | * 171 | * @param args ignored. 172 | */ 173 | public static void main(String[] args) { 174 | double[] xData = {1.0, 2.0, 3.0, 4.0, 5.0}; 175 | double[] yData = {5.0, 7.0, 6.0, 8.0, 4.0}; 176 | LineChart demo = new LineChart(JFreeChartUtil.transfer(xData, yData), "Test Chart", "X", "Y", 500, 350); 177 | demo.pack(); 178 | UIUtils.centerFrameOnScreen(demo); 179 | demo.setVisible(true); 180 | } 181 | 182 | } -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/chart/ScatterChart.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.chart; 2 | 3 | 4 | import org.jfree.chart.ChartFactory; 5 | import org.jfree.chart.ChartPanel; 6 | import org.jfree.chart.JFreeChart; 7 | import org.jfree.chart.axis.NumberAxis; 8 | import org.jfree.chart.plot.XYPlot; 9 | import org.jfree.chart.renderer.xy.XYItemRenderer; 10 | import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer; 11 | import org.jfree.chart.ui.ApplicationFrame; 12 | import org.jfree.chart.ui.RectangleInsets; 13 | import org.jfree.chart.ui.UIUtils; 14 | import org.jfree.data.xy.XYDataset; 15 | 16 | import javax.swing.*; 17 | import java.awt.*; 18 | 19 | /** 20 | * An example of a time series chart create using JFreeChart. For the most 21 | * part, default settings are used, except that the renderer is modified to 22 | * show filled shapes (as well as lines) at each data point. 23 | */ 24 | public class ScatterChart extends ApplicationFrame { 25 | 26 | private static final long serialVersionUID = 1L; 27 | 28 | private final XYDataset xyDataset; 29 | 30 | private final String panelTitle; 31 | private final String xAxisLabel; 32 | private final String yAxisLabel; 33 | 34 | /** 35 | * A demonstration application showing how to create a simple time series 36 | * chart. This example uses monthly data. 37 | * 38 | * @param xyDataset xyDataset. 39 | * @param title the frame title. 40 | * @param xAxisLabel xAxisLabel. 41 | * @param yAxisLabel yAxisLabel. 42 | * @param width width. 43 | * @param height height. 44 | */ 45 | public ScatterChart(XYDataset xyDataset, String title, String xAxisLabel, String yAxisLabel, int width, int height) { 46 | super("Time series chart frame"); 47 | this.xyDataset = xyDataset; 48 | this.panelTitle = title; 49 | this.xAxisLabel = xAxisLabel; 50 | this.yAxisLabel = yAxisLabel; 51 | ChartPanel chartPanel = (ChartPanel) createDemoPanel(); 52 | chartPanel.setPreferredSize(new Dimension(width, height)); 53 | setContentPane(chartPanel); 54 | } 55 | 56 | /** 57 | * Creates a panel for the demo (used by SuperDemo.java). 58 | * 59 | * @return A panel. 60 | */ 61 | public JPanel createDemoPanel() { 62 | JFreeChart chart = createChart(xyDataset); 63 | ChartPanel panel = new ChartPanel(chart, false); 64 | panel.setFillZoomRectangle(true); 65 | panel.setMouseWheelEnabled(true); 66 | return panel; 67 | } 68 | 69 | /** 70 | * Creates a chart. 71 | * 72 | * @param dataset a dataset. 73 | * 74 | * @return A chart. 75 | */ 76 | private JFreeChart createChart(XYDataset dataset) { 77 | 78 | JFreeChart chart = ChartFactory.createScatterPlot( 79 | panelTitle, // title 80 | xAxisLabel, // x-axis label 81 | yAxisLabel, // y-axis label 82 | dataset); 83 | 84 | chart.setBackgroundPaint(Color.WHITE); 85 | 86 | XYPlot plot = (XYPlot) chart.getPlot(); 87 | plot.setBackgroundPaint(Color.LIGHT_GRAY); 88 | plot.setDomainGridlinePaint(Color.WHITE); 89 | plot.setRangeGridlinePaint(Color.WHITE); 90 | plot.setAxisOffset(new RectangleInsets(5.0, 5.0, 5.0, 5.0)); 91 | plot.setDomainCrosshairVisible(true); 92 | plot.setRangeCrosshairVisible(true); 93 | 94 | XYItemRenderer r = plot.getRenderer(); 95 | if (r instanceof XYLineAndShapeRenderer) { 96 | XYLineAndShapeRenderer renderer = (XYLineAndShapeRenderer) r; 97 | renderer.setDefaultShapesVisible(true); 98 | renderer.setDefaultShapesFilled(true); 99 | renderer.setDrawSeriesLineAsPath(true); 100 | } 101 | 102 | NumberAxis xAxis = new NumberAxis("X"); 103 | plot.setDomainAxis(xAxis); 104 | // DateAxis axis = (DateAxis) plot.getDomainAxis(); 105 | // axis.setDateFormatOverride(new SimpleDateFormat("YYYY-MM-dd")); 106 | 107 | return chart; 108 | } 109 | 110 | 111 | /** 112 | * Starting point for the demonstration application. 113 | * 114 | * @param args ignored. 115 | */ 116 | public static void main(String[] args) { 117 | double[] xData = {1.0, 2.0, 3.0, 4.0, 5.0}; 118 | double[] yData = {5.0, 7.0, 6.0, 8.0, 4.0}; 119 | ScatterChart demo = new ScatterChart(JFreeChartUtil.transfer(xData, yData), "Test Chart", "X", "Y", 800, 600); 120 | demo.pack(); 121 | UIUtils.centerFrameOnScreen(demo); 122 | demo.setVisible(true); 123 | } 124 | 125 | } -------------------------------------------------------------------------------- /src/test/java/org/algorithmtools/example/BizUseExample.java: -------------------------------------------------------------------------------- 1 | package org.algorithmtools.example; 2 | 3 | import org.algorithmtools.ad4j.engine.AnomalyDetectionEngine; 4 | import org.algorithmtools.ad4j.pojo.AnomalyDetectionResult; 5 | import org.algorithmtools.ad4j.pojo.IndicatorInfo; 6 | import org.algorithmtools.ad4j.pojo.IndicatorSeries; 7 | import org.algorithmtools.ad4j.utils.IndicatorSeriesUtil; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | public class BizUseExample { 13 | 14 | public static void main(String[] args) { 15 | indicatorDetect(); 16 | } 17 | 18 | public static void indicatorDetect(){ 19 | // 1. Transfer biz data to indicator series info 20 | long currentTime = System.currentTimeMillis(); 21 | List indicatorSeries = new ArrayList<>(); 22 | indicatorSeries.add(new IndicatorSeries(currentTime + 1, 1d, "logicalIndex-1")); 23 | indicatorSeries.add(new IndicatorSeries(currentTime + 2, 2d, "logicalIndex-2")); 24 | indicatorSeries.add(new IndicatorSeries(currentTime + 3, 3d, "logicalIndex-3")); 25 | indicatorSeries.add(new IndicatorSeries(currentTime + 4, 4d, "logicalIndex-4")); 26 | indicatorSeries.add(new IndicatorSeries(currentTime + 5, 40d, "logicalIndex-5")); 27 | indicatorSeries.add(new IndicatorSeries(currentTime + 6, 6d, "logicalIndex-6")); 28 | indicatorSeries.add(new IndicatorSeries(currentTime + 7, 7d, "logicalIndex-7")); 29 | indicatorSeries.add(new IndicatorSeries(currentTime + 8, 8d, "logicalIndex-8")); 30 | indicatorSeries.add(new IndicatorSeries(currentTime + 9, 9d, "logicalIndex-9")); 31 | indicatorSeries.add(new IndicatorSeries(currentTime + 10, 10d, "logicalIndex-10")); 32 | 33 | IndicatorInfo info = new IndicatorInfo("Example", "Example-Name", indicatorSeries); 34 | 35 | // 2. New AnomalyDetectionEngine to detect 36 | AnomalyDetectionEngine engine = new AnomalyDetectionEngine(); 37 | AnomalyDetectionResult detectionResult = engine.detect(info); 38 | 39 | // 3. Business process detect result. Like Records,Alarms,Print 40 | IndicatorSeriesUtil.print(detectionResult); 41 | } 42 | 43 | } 44 | --------------------------------------------------------------------------------