【问题标题】:Java Streams - Standard DeviationJava 流 - 标准偏差
【发布时间】:2016-03-28 13:26:46
【问题描述】:

我想提前澄清一下,我正在寻找一种使用 Streams 计算标准偏差的方法(我目前有一种工作方法可以计算并返回 SD,但不使用 Streams)。

我正在使用的数据集与Link 中的匹配非常接近。如此链接所示,我能够对我的数据进行分组并获得平均值,但无法弄清楚如何获得 SD。

代码

outPut.stream()
            .collect(Collectors.groupingBy(e -> e.getCar(),
                    Collectors.averagingDouble(e -> (e.getHigh() - e.getLow()))))
            .forEach((car,avgHLDifference) -> System.out.println(car+ "\t" + avgHLDifference));

我还在 DoubleSummaryStatistics 上检查了 Link,但它似乎对 SD 没有帮助。

【问题讨论】:

    标签: java java-8 java-stream standard-deviation


    【解决方案1】:

    您可以为此任务使用自定义收集器来计算平方和。内置的DoubleSummaryStatistics 收集器不会跟踪它。专家组in this thread对此进行了讨论,但最终没有实施。计算平方和的难点在于对​​中间结果求平方时可能会溢出。

    static class DoubleStatistics extends DoubleSummaryStatistics {
    
        private double sumOfSquare = 0.0d;
        private double sumOfSquareCompensation; // Low order bits of sum
        private double simpleSumOfSquare; // Used to compute right sum for non-finite inputs
    
        @Override
        public void accept(double value) {
            super.accept(value);
            double squareValue = value * value;
            simpleSumOfSquare += squareValue;
            sumOfSquareWithCompensation(squareValue);
        }
    
        public DoubleStatistics combine(DoubleStatistics other) {
            super.combine(other);
            simpleSumOfSquare += other.simpleSumOfSquare;
            sumOfSquareWithCompensation(other.sumOfSquare);
            sumOfSquareWithCompensation(other.sumOfSquareCompensation);
            return this;
        }
    
        private void sumOfSquareWithCompensation(double value) {
            double tmp = value - sumOfSquareCompensation;
            double velvel = sumOfSquare + tmp; // Little wolf of rounding error
            sumOfSquareCompensation = (velvel - sumOfSquare) - tmp;
            sumOfSquare = velvel;
        }
    
        public double getSumOfSquare() {
            double tmp =  sumOfSquare + sumOfSquareCompensation;
            if (Double.isNaN(tmp) && Double.isInfinite(simpleSumOfSquare)) {
                return simpleSumOfSquare;
            }
            return tmp;
        }
    
        public final double getStandardDeviation() {
            return getCount() > 0 ? Math.sqrt((getSumOfSquare() / getCount()) - Math.pow(getAverage(), 2)) : 0.0d;
        }
    
    }
    

    那么,你就可以用这个类了

    Map<String, Double> standardDeviationMap =
        list.stream()
            .collect(Collectors.groupingBy(
                e -> e.getCar(),
                Collectors.mapping(
                    e -> e.getHigh() - e.getLow(),
                    Collector.of(
                        DoubleStatistics::new,
                        DoubleStatistics::accept,
                        DoubleStatistics::combine,
                        d -> d.getStandardDeviation()
                    )
                )
            ));
    

    这会将输入列表收集到一个映射中,其中值对应于同一键的high - low 的标准偏差。

    【讨论】:

    • 非常感谢。我能够获得 SD。我现在正在检查是否可以在同一个 stream() 调用而不是 2 个流中同时收集 averagingDouble 和 SD(例如 - car、averageHL、SD)。
    • @iCoder 这个答案中的DoubleStatistics 收集SD,平均是。您可以通过Map&lt;String, DoubleStatistics&gt; 获取所有信息。
    • 关于溢出的有趣事实:没有人关心LongSummaryStatistics 实际上溢出了总和,所以LongStream.of(Long.MAX_VALUE, Long.MAX_VALUE).summaryStatistics().getAverage()-1.0。在我看来,达到这个溢出的机会比达到平方和溢出的机会要高......
    • @Tunaki 不太确定我在做什么错误,但是当我将 Map 更改为 Map 时,我收到一条错误消息,无法解析 groupingBy 中的 getScrip() .我想我犯了一些基本的错误,我想需要更多地思考。
    • 在 Java 1.8.0_92 中,示例使用抛出错误:Type mismatch: cannot convert from Map to Map
    【解决方案2】:

    您可以使用此自定义收集器:

    private static final Collector<Double, double[], Double> VARIANCE_COLLECTOR = Collector.of( // See https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
            () -> new double[3], // {count, mean, M2}
            (acu, d) -> { // See chapter about Welford's online algorithm and https://math.stackexchange.com/questions/198336/how-to-calculate-standard-deviation-with-streaming-inputs
                acu[0]++; // Count
                double delta = d - acu[1];
                acu[1] += delta / acu[0]; // Mean
                acu[2] += delta * (d - acu[1]); // M2
            },
            (acuA, acuB) -> { // See chapter about "Parallel algorithm" : only called if stream is parallel ...
                double delta = acuB[1] - acuA[1];
                double count = acuA[0] + acuB[0];
                acuA[2] = acuA[2] + acuB[2] + delta * delta * acuA[0] * acuB[0] / count; // M2
                acuA[1] += delta * acuB[0] / count;  // Mean
                acuA[0] = count; // Count
                return acuA;
            },
            acu -> acu[2] / (acu[0] - 1.0), // Var = M2 / (count - 1)
            UNORDERED);
    

    然后只需在您的流上调用此收集器:

    double stdDev = Math.sqrt(outPut.stream().boxed().collect(VARIANCE_COLLECTOR));
    

    【讨论】:

      猜你喜欢
      • 2018-05-17
      • 1970-01-01
      • 2017-09-26
      • 1970-01-01
      • 2014-05-09
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 2012-12-03
      相关资源
      最近更新 更多