如何检测ArrayList中的异常值

时间:2013-09-14 18:42:25

标签: java arraylist outliers

我正在考虑一些代码,这些代码允许我搜索我的ArrayList并检测“好值”的常见范围之外的任何值。

实施例: 100 105 102 13 104 22 101

我如何能够编写代码来检测(在这种情况下)13和22不在100左右的“良好值”范围内?

8 个答案:

答案 0 :(得分:6)

several criteria用于检测异常值。最简单的那些,如Chauvenet's criterion,使用从样本计算的平均值和标准差来确定值的“正常”范围。超出此范围的任何值都被视为异常值。

其他标准是Grubb's testDixon's Q test,如果样本来自偏斜分布,可能会比Chauvenet提供更好的结果。

答案 1 :(得分:4)

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<Double> data = new ArrayList<Double>();
        data.add((double) 20);
        data.add((double) 65);
        data.add((double) 72);
        data.add((double) 75);
        data.add((double) 77);
        data.add((double) 78);
        data.add((double) 80);
        data.add((double) 81);
        data.add((double) 82);
        data.add((double) 83);
        Collections.sort(data);
        System.out.println(getOutliers(data));
    }

    public static List<Double> getOutliers(List<Double> input) {
        List<Double> output = new ArrayList<Double>();
        List<Double> data1 = new ArrayList<Double>();
        List<Double> data2 = new ArrayList<Double>();
        if (input.size() % 2 == 0) {
            data1 = input.subList(0, input.size() / 2);
            data2 = input.subList(input.size() / 2, input.size());
        } else {
            data1 = input.subList(0, input.size() / 2);
            data2 = input.subList(input.size() / 2 + 1, input.size());
        }
        double q1 = getMedian(data1);
        double q3 = getMedian(data2);
        double iqr = q3 - q1;
        double lowerFence = q1 - 1.5 * iqr;
        double upperFence = q3 + 1.5 * iqr;
        for (int i = 0; i < input.size(); i++) {
            if (input.get(i) < lowerFence || input.get(i) > upperFence)
                output.add(input.get(i));
        }
        return output;
    }

    private static double getMedian(List<Double> data) {
        if (data.size() % 2 == 0)
            return (data.get(data.size() / 2) + data.get(data.size() / 2 - 1)) / 2;
        else
            return data.get(data.size() / 2);
    }
}

输出: [20.0]

说明:

  • 对整数列表进行排序,从低到高
  • 将整数列表拆分为2个部分(中间)并将它们放入2个新的独立ArrayLists(称为“左”和“右”)
  • 在这两个新的ArrayLists中找到一个中间数字(中位数)
  • Q1是左侧的中位数,Q3是右侧的中位数
  • 应用数学公式:
  • IQR = Q3 - Q1
  • LowerFence = Q1 - 1.5 * IQR
  • UpperFence = Q3 + 1.5 * IQR
  • 有关此公式的更多信息:http://www.mathwords.com/o/outlier.htm
  • 遍历我的所有原始元素,如果其中任何一个低于下栅栏,或高于上栅栏,则将它们添加到 “output”ArrayList
  • 这个新的“输出”ArrayList包含异常值

答案 2 :(得分:2)

可以在Grubb's test找到MathUtil.java的实施。它会找到一个异常值,您可以从列表中删除并重复,直到您删除了所有异常值。

取决于commons-math,所以如果你正在使用Gradle:

dependencies {
  compile 'org.apache.commons:commons-math:2.2'
}

答案 3 :(得分:1)

  • 查找列表的平均值
  • 创建一个Map,将数字映射到距平均值
  • 的距离
  • 按距平均值
  • 的距离对值进行排序
  • 并区分最后n个号码,确保距离不存在不公正

答案 4 :(得分:1)

使用此算法。该算法使用平均值和标准差。这2个数字可选值(2 * standardDeviation)。

 public static List<int> StatisticalOutLierAnalysis(List<int> allNumbers)
            {
                if (allNumbers.Count == 0)
                    return null;

                List<int> normalNumbers = new List<int>();
                List<int> outLierNumbers = new List<int>();
                double avg = allNumbers.Average();
                double standardDeviation = Math.Sqrt(allNumbers.Average(v => Math.Pow(v - avg, 2)));
                foreach (int number in allNumbers)
                {
                    if ((Math.Abs(number - avg)) > (2 * standardDeviation))
                        outLierNumbers.Add(number);
                    else
                        normalNumbers.Add(number);
                }

                return normalNumbers;
            }

答案 5 :(得分:0)

这只是一个非常简单的实现,可以获取数字不在范围内的信息:

List<Integer> notInRangeNumbers = new ArrayList<Integer>();
for (Integer number : numbers) {
    if (!isInRange(number)) {
        // call with a predefined factor value, here example value = 5
        notInRangeNumbers.add(number, 5);
    }
}

此外,在isInRange方法中,您必须定义“良好价值”的含义。您将在下面找到一个示例性实现。

private boolean isInRange(Integer number, int aroundFactor) {
   //TODO the implementation of the 'in range condition'
   // here the example implementation
   return number <= 100 + aroundFactor && number >= 100 - aroundFactor;
}

答案 6 :(得分:0)

正如Joni所指出的,您可以借助标准偏差和均值消除异常值。这是我的代码,可用于您的目的。

    public static void main(String[] args) {

    List<Integer> values = new ArrayList<>();
    values.add(100);
    values.add(105);
    values.add(102);
    values.add(13);
    values.add(104);
    values.add(22);
    values.add(101);

    System.out.println("Before: " + values);
    System.out.println("After: " + eliminateOutliers(values,1.5f));

}

protected static double getMean(List<Integer> values) {
    int sum = 0;
    for (int value : values) {
        sum += value;
    }

    return (sum / values.size());
}

public static double getVariance(List<Integer> values) {
    double mean = getMean(values);
    int temp = 0;

    for (int a : values) {
        temp += (a - mean) * (a - mean);
    }

    return temp / (values.size() - 1);
}

public static double getStdDev(List<Integer> values) {
    return Math.sqrt(getVariance(values));
}

public static List<Integer> eliminateOutliers(List<Integer> values, float scaleOfElimination) {
    double mean = getMean(values);
    double stdDev = getStdDev(values);

    final List<Integer> newList = new ArrayList<>();

    for (int value : values) {
        boolean isLessThanLowerBound = value < mean - stdDev * scaleOfElimination;
        boolean isGreaterThanUpperBound = value > mean + stdDev * scaleOfElimination;
        boolean isOutOfBounds = isLessThanLowerBound || isGreaterThanUpperBound;

        if (!isOutOfBounds) {
            newList.add(value);
        }
    }

    int countOfOutliers = values.size() - newList.size();
    if (countOfOutliers == 0) {
        return values;
    }

    return eliminateOutliers(newList,scaleOfElimination);
}
  • eliminateOutliers()方法正在完成所有工作
  • 这是一种递归方法,每次递归调用都会修改列表
  • 您传递给方法的
  • scaleOfElimination变量定义了什么比例 您要删除离群值:通常我使用1.5f-2f,变量越大, 较少的异常值将被删除

代码输出:

  

之前:[100,105,102,13,104,22,101]

     

之后:[100,105,102,104,101]

答案 7 :(得分:0)

我非常高兴,感谢Valiyev。他的解决方案对我有很大帮助。而且我想在他的作品上保留我的小SRP。

请注意,我使用List.of()存储Dixon的关键值,因此,必须使用高于8的Java。

public class DixonTest {
protected List<Double> criticalValues = 
    List.of(0.941, 0.765, 0.642, 0.56, 0.507, 0.468, 0.437);
private double scaleOfElimination;
private double mean;
private double stdDev;

private double getMean(final List<Double> input) {
    double sum = input.stream()
            .mapToDouble(value -> value)
            .sum();
    return (sum / input.size());
}

  private double getVariance(List<Double> input) {
    double mean = getMean(input);
    double temp = input.stream()
            .mapToDouble(a -> a)
            .map(a -> (a - mean) * (a - mean))
            .sum();
    return temp / (input.size() - 1);
}

private double getStdDev(List<Double> input) {
    return Math.sqrt(getVariance(input));
}

protected List<Double> eliminateOutliers(List<Double> input) {
    int N = input.size() - 3;
    scaleOfElimination = criticalValues.get(N).floatValue();
    mean = getMean(input);
    stdDev = getStdDev(input);

    return input.stream()
            .filter(this::isOutOfBounds)
            .collect(Collectors.toList());
}

private boolean isOutOfBounds(Double value) {
    return !(isLessThanLowerBound(value)
            || isGreaterThanUpperBound(value));
}

private boolean isGreaterThanUpperBound(Double value) {
    return value > mean + stdDev * scaleOfElimination;
}

private boolean isLessThanLowerBound(Double value) {
    return value < mean - stdDev * scaleOfElimination;
}
}

我希望它会帮助别人。

最诚挚的问候