13

假设我对一组返回以下数字的数据库记录进行抽样:

20.50, 80.30, 70.95, 15.25, 99.97, 85.56, 69.77

是否有一种算法可以在 PHP 中有效地实现,以根据它们偏离平均值的距离从浮点数组中找到异常值(如果有的话)?

4

3 回答 3

34

好的,假设您将数据点放在一个数组中,如下所示:

<?php $dataset = array(20.50, 80.30, 70.95, 15.25, 99.97, 85.56, 69.77); ?>

然后,您可以使用以下函数(有关发生的情况,请参阅注释)删除所有超出平均值 +/- 标准偏差乘以您设置的幅度的数字(默认为 1):

<?php

function remove_outliers($dataset, $magnitude = 1) {

  $count = count($dataset);
  $mean = array_sum($dataset) / $count; // Calculate the mean
  $deviation = sqrt(array_sum(array_map("sd_square", $dataset, array_fill(0, $count, $mean))) / $count) * $magnitude; // Calculate standard deviation and times by magnitude

  return array_filter($dataset, function($x) use ($mean, $deviation) { return ($x <= $mean + $deviation && $x >= $mean - $deviation); }); // Return filtered array of values that lie within $mean +- $deviation.
}

function sd_square($x, $mean) {
  return pow($x - $mean, 2);
} 

?>

对于您的示例,此函数返回以下值为 1 的值:

Array
(
    [1] => 80.3
    [2] => 70.95
    [5] => 85.56
    [6] => 69.77
)
于 2013-03-02T14:02:56.720 回答
1

对于一组正态分布的数据,从平均值中删除超过 3 个标准差的值。

<?php
function remove_outliers($array) {
    if(count($array) == 0) {
      return $array;
    }
    $ret = array();
    $mean = array_sum($array)/count($array);
    $stddev = stats_standard_deviation($array);
    $outlier = 3 * $stddev;
    foreach($array as $a) {
        if(!abs($a - $mean) > $outlier) {
            $ret[] = $a;
        }
    }
    return $ret;
}
于 2013-03-02T13:46:09.833 回答
0

主题:通过在数组中遍历一个小窗口并计算某个值范围的标准偏差,检测无序数组中的局部、加性异常值。

各位早上好

这是我的解决方案很晚了,但是由于我正在寻找通过 PHP 检测异常值并且找不到任何基本的东西,我决定通过简单地连续移动一系列 5 个项目来以某种方式在 24 小时的时间线上平滑给定的数据集通过无序数组并计算局部标准偏差以检测加性异常值。

第一个函数将简单地计算给定数组的平均值和偏差,其中 $col 表示具有值的列(对于 freegrades 抱歉,这意味着在 5 个值的不完整数据集中你只有 4 个 freegrades - 我没有知道 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 "&diams;".$arr[$i][1]."&diams; "; 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

我希望,这将有助于现在或将来的任何人。问候

PS 为了进一步改进此算法,您可以添加一个计数器,以确保某个值必须例如至少找到 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 次。像往常一样,我们必须仔细观察边界,因为它们只有一个间隔,并且将这些值设为例外。

于 2018-08-24T08:56:22.680 回答