JavaScript:如何从数组中移除离群值?

18
values = [8160,8160,6160,22684,0,0,60720,1380,1380,57128]

如何移除像0、57218、60720和22684这样的异常值?

是否有可以完成此操作的库?


你可能想要查看这个答案:https://dev59.com/CG025IYBdhLWcg3w1ZoL#5767357。如果你想要查看一个库,可以试试underscore。这个答案有一个例子:https://dev59.com/wG865IYBdhLWcg3wKLT6#14954540。 - EfrainReyes
你也可以查看这个库链接 - Mozak
5个回答

27

这完全取决于您对“异常值”是什么的解释。一个常见的方法:

  • 高异常值是指超过第三四分位数加上1.5倍的四分位距(IQR)的任何数据
  • 低异常值是指低于第一四分位数减去1.5倍IQR的任何数据

这也是Wolfram的Mathworld所描述的方法。

这可以轻松地封装在一个函数中 :) 我已尽力清晰地编写以下内容;显然存在重构的机会。请注意,在使用这种常见方法时,样本中没有包含任何异常值

function filterOutliers(someArray) {  

    // Copy the values, rather than operating on references to existing values
    var values = someArray.concat();

    // Then sort
    values.sort( function(a, b) {
            return a - b;
         });

    /* Then find a generous IQR. This is generous because if (values.length / 4) 
     * is not an int, then really you should average the two elements on either 
     * side to find q1.
     */     
    var q1 = values[Math.floor((values.length / 4))];
    // Likewise for q3. 
    var q3 = values[Math.ceil((values.length * (3 / 4)))];
    var iqr = q3 - q1;

    // Then find min and max values
    var maxValue = q3 + iqr*1.5;
    var minValue = q1 - iqr*1.5;

    // Then filter anything beyond or beneath these values.
    var filteredValues = values.filter(function(x) {
        return (x <= maxValue) && (x >= minValue);
    });

    // Then return
    return filteredValues;
}

3
它能起作用吗?我尝试了 filterOutliers([8160,8160,6160,22684,0,0,60720,1380,1380,57128, 1000000000000]) ,它返回的是完全相同的数组。 - Pablo
1
以上代码存在轻微的逻辑错误。筛选器应该返回 (x < maxValue) && (x > minValue); - Algonomaly
1
如果 q1===q3,则返回空数组。应该返回 (x <= maxValue) && (x >= minValue) - Timo Kähkönen
2
[4421, 3512, 5126, 6012, 7581, 2023, 5012, 2320, 17, 2125] 没有移除 17,这是怎么回事呢?难道 17 在这里是一个异常值吗? - Frank
2
@Frank:17不是异常值。你数组的下限(第一象限-1.5*(四分位距))比17要低得多。 - mukund
显示剩余2条评论

9
这是@james-peterson解决方案的改进版,更新了语法到当前JavaScript标准,并添加了一种更强大的找到两个四分位数的方法(根据https://de.wikipedia.org/wiki/Interquartilsabstand_(Deskriptive_Statistik)上的公式进行实现)。它使用了更快速的数组复制方式(请参见http://jsben.ch/wQ9RU进行性能比较),并且仍然适用于q1 = q3。
function filterOutliers(someArray) {

  if(someArray.length < 4)
    return someArray;

  let values, q1, q3, iqr, maxValue, minValue;

  values = someArray.slice().sort( (a, b) => a - b);//copy array fast and sort

  if((values.length / 4) % 1 === 0){//find quartiles
    q1 = 1/2 * (values[(values.length / 4)] + values[(values.length / 4) + 1]);
    q3 = 1/2 * (values[(values.length * (3 / 4))] + values[(values.length * (3 / 4)) + 1]);
  } else {
    q1 = values[Math.floor(values.length / 4 + 1)];
    q3 = values[Math.ceil(values.length * (3 / 4) + 1)];
  }

  iqr = q3 - q1;
  maxValue = q3 + iqr * 1.5;
  minValue = q1 - iqr * 1.5;

  return values.filter((x) => (x >= minValue) && (x <= maxValue));
}

请查看此代码片段:https://gist.github.com/rmeissn/f5b42fb3e1386a46f60304a57b6d215a

1
第二个条件语句对于数组长度小于7的任何内容都不起作用,因为q3最终会超出边界,即Math.ceil(7 * (3/4) + 1) = 7。我猜应该使用Math.min来解决这个问题。 - Dominic
如果数组长度为4,则第一个条件语句中的q3将是NaN,因为values[(values.length * (3 / 4)) + 1]指向空。因此,如果长度<=4,则应该退出。 - Dominic

4

我在其他两种解决方案中遇到了一些问题。由于索引错误,在q1和q3中有NaN值的问题。数组长度需要减1,因为索引从0开始。然后检查索引是否为整数或小数,在小数的情况下,提取两个索引之间的值。

function filterOutliers (someArray) {
    if (someArray.length < 4) {
        return someArray;
    }

    let values = someArray.slice().sort((a, b) => a - b); // copy array fast and sort

    let q1 = getQuantile(values, 25);
    let q3 = getQuantile(values, 75);

    let iqr, maxValue, minValue;
    iqr = q3 - q1;
    maxValue = q3 + iqr * 1.5;
    minValue = q1 - iqr * 1.5;

    return values.filter((x) => (x >= minValue) && (x <= maxValue));
}

function getQuantile (array, quantile) {
    // Get the index the quantile is at.
    let index = quantile / 100.0 * (array.length - 1);

    // Check if it has decimal places.
    if (index % 1 === 0) {
        return array[index];
    } else {
        // Get the lower index.
        let lowerIndex = Math.floor(index);
        // Get the remaining.
        let remainder = index - lowerIndex;
        // Add the remaining to the lowerindex value.
        return array[lowerIndex] + remainder * (array[lowerIndex + 1] - array[lowerIndex]);
    }
}

3
这里是从给定集合中过滤上限异常值的实现方法。该方法遵循与上面提供的答案类似的方法论。
如果集合长度为4n或4n + 1,则if语句将检查集合长度。在这种情况下,我们需要获取两个元素的平均值来得到四分位数。

4n and 4n+1 cases

否则,在4n + 2和4n + 3的情况下,我们可以直接访问上/下四分位数。

4n+2 and 4n+3 cases


const outlierDetector = collection => {
    const size = collection.length;

    let q1, q3;

    if (size < 2) {
        return collection;
    }

    const sortedCollection = collection.slice().sort((a, b) => a - b);

    if ((size - 1) / 4 % 1 === 0 || size / 4 % 1 === 0) {
        q1 = 1 / 2 * (sortedCollection[Math.floor(size / 4) - 1] + sortedCollection[Math.floor(size / 4)]);
        q3 = 1 / 2 * (sortedCollection[Math.ceil(size * 3 / 4) - 1] + sortedCollection[Math.ceil(size * 3 / 4)]);
    } else {
        q1 = sortedCollection[Math.floor(size / 4)];
        q3 = sortedCollection[Math.floor(size * 3 / 4)];
    }

    const iqr = q3 - q1;
    const maxValue = q3 + iqr * 1.5;

    return sortedCollection.filter(value => value >= maxValue);
};


-1

如果你的数据集包含重复值,那么这种方法实际上会失败。例如:1, 2, 2, 2, 2, 2, 3, 10

我曾经为此苦苦挣扎,但后来我发现了一种叫做 Grubbs 测试的东西。到目前为止,在我的情况下它似乎是可靠的。

这里有一个演示链接(和源代码):http://xcatliu.com/grubbs/


嘿,这个链接已经失效了,你能发一下代码吗? - Ben
1
@BenHickson 我相信这是链接:https://github.com/xcatliu/grubbs。已经有一段时间了,所以我不太记得具体细节了。 - xb1itz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接