在PHP中查找和删除异常值

时间:2013-03-02 13:20:37

标签: php algorithm

假设我选择了一些返回以下数字的数据库记录:

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

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

3 个答案:

答案 0 :(得分:26)

好的,我们假设你的数据点是这样的数组:

<?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
)

答案 1 :(得分: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;
}

答案 2 :(得分:0)

主题:通过在数组中穿行一个小窗口并计算一定范围内的值的标准偏差,来检测无序数组中的局部加法异常值。

早上好,

这是我最近的解决方案,但是由于我一直在寻找通过PHP检测异常值并且找不到基本的方法,因此我决定以某种方式通过仅移动5个项目的范围在24小时的时间内平滑给定的数据集通过无序数组排成一行并计算局部标准差以检测加法离群值。

第一个函数将简单地计算给定数组的平均值和偏差,其中$ col表示具有值的列(对不起,对于freegrade,这意味着在5个值的不完整数据集中,您只有4个freegrade-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 "&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   350232265♦63♦90 94

     

228♦392♦130 134 170♦485♦17 463 13 326 47 439 430 151 268 172 342   445477♦21♦421440219 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   297337204210106149

     

345 411 308 360 308 346♦451♦♦77♦16498331 160 142 102♦496♦220   107143♦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♦436290384232

     

442 302♦436♦50 114 15 21 93♦376♦416439♦222♦398237234 44 102   464204421161330396461

     

498 320 105 22 281 168 381 216 435 360 19♦402♦131 128 66 187 291 459   31943386 84325247

     

440491381491♦22♦412 33273256331 79452314485 66138116356   290190336178178298218

     

394 439 387♦80♦463 369♦104♦388 465 455♦246♦499 70431 360♦22♦   203280241319♦34♦238439497

     

485 289 249♦416♦228 166 217 186 184♦356♦142 166 26 91 70♦466♦177   357298443307307387373209

     

338 166 90 122 442 429 499 293♦41♦159395 79 307 91 325 91162211   85189278251224481

     

77 196 37 326 230 281♦73♦334 159 490 127 365 37 57 246 26 285 468   228181 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

     

216213249219♦29♦25510021618123333 47344344383♦94♦323440   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♦362214 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

我希望,这将对现在或将来的所有人有所帮助。 问候

P.S。为了进一步改进该算法,您可以添加一个计数器,以确保一定值必须至少被发现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个间隔计数,因此是一个异常值。因此,如果它至少“计数”两次,则让我们声明一个异常值。 通常,我们不得不仔细观察边界,因为边界只有一个间隔,并且将这些值设为例外。