主题:通过在数组中遍历一个小窗口并计算特定范围值的标准差,检测无序数组中的局部、加性异常值。
大家早上好,
这是我的解决方案很晚了,但由于我正在寻找通过 PHP 检测异常值并且找不到任何基本的东西,我决定通过简单地移动 5 个项目的范围以某种方式在 24 小时的时间线上平滑给定的数据集连续通过无序数组并计算局部标准偏差以检测加性异常值。
第一个函数将简单地计算给定数组的平均值和偏差,其中 $col 表示具有值的列(对于 freegrades 抱歉,这意味着在 5 个值的不完整数据集中,您只有 4 个 freegrades - I不知道 Freiheitsgrade 的确切英文单词):
function analytics_stat ($arr,$col,$freegrades = 0) {
// calculate average called mu
$mu = 0;
foreach ($arr as $row) {
$mu += $row[$col];
}
$mu = $mu / count($arr);
// calculate empiric standard deviation called sigma
$sigma = 0;
foreach ($arr as $row) {
$sigma += pow(($mu - $row[$col]),2);
}
$sigma = sqrt($sigma / (count($arr) - $freegrades));
return [$mu,$sigma];
}
现在是核心函数的时候了,它将遍历给定的数组并使用结果创建一个新数组。边距是指与偏差相乘的因子,因为只有一个 Sigma 检测到许多异常值,而超过 1.7 似乎很高:
function analytics_detect_local_outliers ($arr,$col,$range,$margin = 1.0) {
$count = count($arr);
if ($count < $range) return false;
// the initial state of each value is NOT OUTLIER
$arr_result = [];
for ($i = 0;$i < $count;$i++) {
$arr_result[$i] = false;
}
$max = $count - $range + 1;
for ($i = 0;$i < $max;$i++) {
// calculate mu and sigma for current interval
// remember that 5 values will determine the divisor 4 for sigma
// since we only look at a part of the hole data set
$stat = analytics_stat(array_slice($arr,$i,$range),$col,1);
// a value in this interval counts, if it's found outside our defined sigma interval
$range_max = $i + $range;
for ($j = $i;$j < $range_max;$j++) {
if (abs($arr[$j][$col] - $stat[0]) > $margin * $stat[1]) {
$arr_result[$j] = true;
// this would be the place to add a counter to isolate
// real outliers from sudden steps in our data set
}
}
}
return $arr_result;
}
最后是一个长度为 24 的数组中带有随机值的测试函数。
至于边距,我很好奇并选择了 Golden Cut PHI = 1.618 ... 因为我真的很喜欢这个数字,并且一些 Excel 测试结果使我达到了 1.7 的边距,在此之上很少检测到异常值。 5 的范围是可变的,但对我来说这已经足够了。因此,对于一行中的每 5 个值,就会有一个计算:
function test_outliers () {
// create 2 dimensional data array with items [hour,value]
$arr = [];
for ($i = 0;$i < 24;$i++) {
$arr[$i] = [$i,rand(0,500)];
}
// set parameter for detection algorithm
$result = [];
$col = 1;
$range = 5;
$margin = 1.618;
$result = analytics_detect_local_outliers ($arr,$col,$range,$margin);
// display results
echo "<p style='font-size:8pt;'>";
for ($i = 0;$i < 24;$i++) {
if ($result[$i]) echo "♦".$arr[$i][1]."♦ "; else echo $arr[$i][1]." ";
}
echo "</p>";
}
在调用了 20 次测试函数后,我得到了这些结果:
417 140 372 131 449 26 192 222 320 349 94 147 201 ♦342♦ 123 16 15
♦490♦ 78 190 ♦434♦ 27 3 276
379 440 198 135 22 461 208 376 286 ♦73♦ 331 358 341 14 112 190 110 266
350 232 265 ♦63♦ 90 94
228 ♦392♦ 130 134 170 ♦485♦ 17 463 13 326 47 439 430 151 268 172 342
445 477 ♦21♦ 421 440 219 95
88 121 292 255 ♦16♦ 223 244 109 127 231 370 16 93 379 218 87 ♦335♦ 150
84 181 25 280 15 406
85 252 310 122 188 302 ♦13♦ 439 254 414 423 216 456 321 85 61 215 7
297 337 204 210 106 149
345 411 308 360 308 346 ♦451♦ ♦77♦ 16 498 331 160 142 102 ♦496♦ 220
107 143 ♦241♦ 113 82 355 114 452
490 222 412 94 2 ♦480♦ 181 149 41 110 220 ♦477♦ 278 349 73 186 135 181
♦39♦ 136 284 340 165 438
147 311 246 449 396 328 330 280 453 374 214 289 489 185 445 86 426 246
319 ♦30♦ 436 290 384 232
442 302 ♦436♦ 50 114 15 21 93 ♦376♦ 416 439 ♦222♦ 398 237 234 44 102
464 204 421 161 330 396 461
498 320 105 22 281 168 381 216 435 360 19 ♦402♦ 131 128 66 187 291 459
319 433 86 84 325 247
440 491 381 491 ♦22♦ 412 33 273 256 331 79 452 314 485 66 138 116 356
290 190 336 178 298 218
394 439 387 ♦80♦ 463 369 ♦104♦ 388 465 455 ♦246♦ 499 70 431 360 ♦22♦
203 280 241 319 ♦34♦ 238 439 497
485 289 249 ♦416♦ 228 166 217 186 184 ♦356♦ 142 166 26 91 70 ♦466♦ 177
357 298 443 307 387 373 209
338 166 90 122 442 429 499 293 ♦41♦ 159 395 79 307 91 325 91 162 211
85 189 278 251 224 481
77 196 37 326 230 281 ♦73♦ 334 159 490 127 365 37 57 246 26 285 468
228 181 74 ♦455♦ 119 435
328 3 216 149 217 348 65 433 164 473 465 145 341 112 462 396 168 251
351 43 320 123 181 198
216 213 249 219 ♦29♦ 255 100 216 181 233 33 47 344 383 ♦94♦ 323 440
187 79 403 139 382 37 395
366 450 263 160 290 ♦126♦ 304 307 335 396 458 195 171 493 270 434 222
401 38 383 158 355 311 150
402 339 382 97 125 88 300 332 250 ♦86♦ 362 214 448 67 114 ♦354♦ 140 16
♦354♦ 109 0 168 127 89
450 5 232 155 159 264 214 ♦416♦ 51 429 372 230 298 232 251 207 ♦322♦
160 148 206 293 446 111 338
我希望,这对现在或将来的任何人都有帮助。
问候
附:为了进一步改进此算法,您可以添加一个计数器,以确保某个值必须例如至少找到 2 次,这意味着在 2 个不同的间隔或窗口中,在它被标记为异常值之前。因此,以下值的突然跳跃不会使第一个值成为反派。举个例子吧:
在 3,6,5,9,37,40,42,51,98,39,33,45 中有一个明显的从 9 到 37 的步长和一个孤立的值 98。我想检测 98,但是不是 9 或 37。
第一个区间 3,6,5,9,37 会检测到 37,第二个区间 6,5,9,37,40 不会。所以我们不会检测到 37,因为只有一个有问题的间隔或一个匹配。现在应该清楚了,98 在 5 个间隔中计数,因此是一个异常值。因此,让我们将一个值声明为异常值,如果它“计数”至少 2 次。
像往常一样,我们必须仔细查看边界,因为它们只有一个间隔,并将这些值设为例外。