【问题标题】:How to detect outliers in an ArrayList如何检测 ArrayList 中的异常值
【发布时间】:2013-09-19 06:06:56
【问题描述】:

我正在尝试编写一些代码,让我可以搜索我的 ArrayList 并检测“好值”的常见范围之外的任何值。

示例: 100 105 102 13 104 22 101

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

【问题讨论】:

  • 您需要严格定义“良好价值观”的含义。它们的值是否超过平均值的 x 标准偏差?还是别的什么?
  • 这可以通过一些if 语句轻松完成
  • 你能解释一下如何用 if 语句来做到这一点@redFIVE。谢谢
  • @redFIVE 我只是想确保我得到了正确的起点。我知道 if 语句是一个布尔比较,仅当且仅当布尔比较通过时才执行嵌套在比较下的块中的语句,返回值 1 而不是 0。但是,感谢您的输入。我考虑过使用 if 语句并在循环内比较这两个变量的值是否大于 5 或​​小于 -5。但是,我在确定如何检测应该删除的元素时遇到了问题。

标签: java arraylist outliers


【解决方案1】:

感谢@Emil_Wozniak 发布完整代码。我为此苦苦挣扎了一段时间,没有意识到eliminateOutliers() 实际上返回了异常值,而不是消除了它们的列表。 isOutOfBounds() 方法也令人困惑,因为它实际上在值为 IN 边界时返回 TRUE。以下是我对一些(恕我直言)改进的更新:

  • eliminateOutliers() 方法返回已删除异常值的输入列表
  • 添加了 getOutliers() 方法来获取异常值列表
  • 删除了令人困惑的 isOutOfBounds() 方法以支持简单的过滤表达式
  • 扩展了 N 列表以支持多达 30 个输入值
  • 防止输入列表太大或太小时出现越界错误
  • Made stats 方法(mean、stddev、variance)静态实用方法
  • 只计算一次上限/下限,而不是每次比较时计算
  • 在 ctor 上提供输入列表并存储为实例变量
  • 重构以避免使用与实例和局部变量相同的变量名

代码:

/**
 * Implements an outlier removal algorithm based on https://www.itl.nist.gov/div898/software/dataplot/refman1/auxillar/dixon.htm#:~:text=It%20can%20be%20used%20to,but%20one%20or%20two%20observations).
 * Original Java code by Emil Wozniak at https://stackoverflow.com/questions/18805178/how-to-detect-outliers-in-an-arraylist
 * 
 * Reorganized, made more robust, and clarified many of the methods.
 */

import java.util.List;
import java.util.stream.Collectors;

public class DixonTest {
    protected List<Double> criticalValues = 
            List.of( // Taken from https://sebastianraschka.com/Articles/2014_dixon_test.html#2-calculate-q
                    // Alfa level of 0.1 (90% confidence)
                    0.941,  // N=3
                    0.765,  // N=4
                    0.642,  // ...
                    0.56,
                    0.507,
                    0.468,
                    0.437,
                    0.412,
                    0.392,
                    0.376,
                    0.361,
                    0.349,
                    0.338,
                    0.329,
                    0.32,
                    0.313,
                    0.306,
                    0.3,
                    0.295,
                    0.29,
                    0.285,
                    0.281,
                    0.277,
                    0.273,
                    0.269,
                    0.266,
                    0.263,
                    0.26     // N=30
                    );
    
    // Stats calculated on original input data (including outliers)
    private double scaleOfElimination;
    private double mean;
    private double stdDev;
    private double UB;
    private double LB;
    private List<Double> input;
    
    /**
     * Ctor taking a list of values to be analyzed. 
     * @param input
     */
    public DixonTest(List<Double> input) {
        this.input = input;
        
        // Create statistics on the original input data
        calcStats();
    }

    /**
     * Utility method returns the mean of a list of values.
     * @param valueList
     * @return
     */
    public static double getMean(final List<Double> valueList) {
        double sum = valueList.stream()
                .mapToDouble(value -> value)
                .sum();
        return (sum / valueList.size());
    }

    /**
     * Utility method returns the variance of a list of values.
     * @param valueList
     * @return
     */
    public static double getVariance(List<Double> valueList) {
        double listMean = getMean(valueList);
        double temp = valueList.stream()
                .mapToDouble(a -> a)
                .map(a -> (a - listMean) * (a - listMean))
                .sum();
        return temp / (valueList.size() - 1);
    }

    /**
     * Utility method returns the std deviation of a list of values.
     * @param input
     * @return
     */
    public static double getStdDev(List<Double> valueList) {
        return Math.sqrt(getVariance(valueList));
    }
    
    /**
     * Calculate statistics and bounds from the input values and store
     * them in class variables.
     * @param input
     */
    private void calcStats() {
        int N = Math.min(Math.max(0, input.size() - 3), criticalValues.size()-1); // Changed to protect against too-small or too-large lists
        scaleOfElimination = criticalValues.get(N).floatValue();
        mean = getMean(input);
        stdDev = getStdDev(input);
        UB = mean + stdDev * scaleOfElimination;
        LB = mean - stdDev * scaleOfElimination;        
    }

    /**
     * Returns the input values with outliers removed.
     * @param input
     * @return
     */
    public List<Double> eliminateOutliers() {

        return input.stream()
                .filter(value -> value>=LB && value <=UB)
                .collect(Collectors.toList());
    }

    /**
     * Returns the outliers found in the input list.
     * @param input
     * @return
     */
    public List<Double> getOutliers() {

        return input.stream()
                .filter(value -> value<LB || value>UB)
                .collect(Collectors.toList());
    }

    /**
     * Test and sample usage
     * @param args
     */
    public static void main(String[] args) {
        List<Double> testValues = List.of(1200.0,1205.0,1220.0,1194.0,1212.0);
        
        DixonTest outlierDetector = new DixonTest(testValues);
        List<Double> goodValues = outlierDetector.eliminateOutliers();
        List<Double> badValues = outlierDetector.getOutliers();
        
        System.out.println(goodValues.size()+ " good values:");
        for (double v: goodValues) {
            System.out.println(v);
        }
        System.out.println(badValues.size()+" outliers detected:");
        for (double v: badValues) {
            System.out.println(v);
        }
        
        // Get stats on remaining (good) values
        System.out.println("\nMean of good values is "+DixonTest.getMean(goodValues));
    }
}

【讨论】:

    【解决方案2】:

    我很高兴并感谢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;
    }
    }
    

    我希望它会帮助别人。

    最好的尊重

    【讨论】:

      【解决方案3】:

      正如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]

      【讨论】:

        【解决方案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 个新的单独的 ArrayList(称它们为“左”和“右”)
        • 在这两个新的 ArrayList 中找到一个中间数(中位数)
        • Q1 是左侧的中位数,Q3 是右侧的中位数
        • 应用数学公式:
        • IQR = Q3 - Q1
        • Lo​​werFence = Q1 - 1.5*IQR
        • UpperFence = Q3 + 1.5*IQR
        • 关于这个公式的更多信息:http://www.mathwords.com/o/outlier.htm
        • 循环遍历我所有的原始元素,如果它们中的任何一个低于下栅栏或高于上栅栏,请将它们添加到 “输出”数组列表
        • 这个新的“输出”ArrayList 包含异常值

        【讨论】:

        • @Al0x ...我刚刚为我的答案添加了解释。看看吧
        • 这段代码非常糟糕。它假定输入已排序。如果 data 为 null 或 data.getSize() == 1,getMedian 会出现错误
        • @MladenAdamovic:一般来说,来自 Stackoverflow 的代码应该更多地被视为对其他人的指导,而不是“生产代码,准备好被复制/粘贴”,至少,这是专业工程师所做的。基于边缘情况进行批评总是比像 sklimkovitch 那样编写完整的算法更容易。就像流行歌曲所说:“谦虚”;-)
        【解决方案5】:

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

        取决于commons-math,所以如果您使用的是 Gradle:

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

        【讨论】:

          【解决方案6】:

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

           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;
                      }
          

          【讨论】:

          【解决方案7】:

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

          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;
          }
          

          【讨论】:

          • 我真的很喜欢你的想法,但我不能在我的程序中使用它,特别是。数据集可以是任何一组数字,但大多数都在某个值附近。不知道该值,是否仍然可以执行您的方法?谢谢。
          • @Dan 你是什么意思,这些数字在某个值附近,但不知道那个值。我猜这个值必须以某种方式硬编码/预定义。您能否扩展您的问题并描述您真正想要实现的目标,因为我看到 cmets 并不完全清楚。
          • 抱歉不清楚。我只想找到一个“范围平均值”,首先从输入中检查数据集是否存在异常值或异常值,将它们从 arrayList 中删除,然后计算平均值。
          • @Dan Ok 所以看来您必须实施 Joni 提出的一些标准。当然,您可以调整我的代码来检查一个数字是否是异常值,但是现在很清楚重点在哪里。请参阅gist.github.com/sushain97/6488296 有一些 Chauvenet 异常值标准的示例
          【解决方案8】:

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

          其他标准是 Grubb's testDixon's Q test,并且可能比 Chauvenet 的结果更好,例如,如果样本来自偏态分布。

          【讨论】:

          • 我不确定我计算的标准差是否错误。在我的 JUnit 中,我有 10、12、11、25、13、14 作为我的数组。我计算的标准偏差为 5.----。我不确定如何解释这个答案以在我的数据中使用作为一个因素。
          【解决方案9】:
          • 找出列表的平均值
          • 创建一个Map,将数字映射到与平均值的距离
          • 按与平均值的距离对值进行排序
          • 并区分最后一个n 数字,确保距离没有不公平

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2019-07-24
            • 1970-01-01
            • 1970-01-01
            • 2018-12-06
            • 2019-03-09
            • 2020-04-03
            • 2020-02-23
            • 1970-01-01
            相关资源
            最近更新 更多