001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.statistics.descriptive; 018 019/** 020 * Computes the geometric mean of the available values. Uses the following definition 021 * of the geometric mean: 022 * 023 * <p>\[ \left(\prod_{i=1}^n x_i\right)^\frac{1}{n} \] 024 * 025 * <p>where \( n \) is the number of samples. This implementation uses the log scale: 026 * 027 * <p>\[ \exp{\left( {\frac{1}{n}\sum_{i=1}^n \ln x_i} \right)} \] 028 * 029 * <ul> 030 * <li>The result is {@code NaN} if no values are added. 031 * <li>The result is {@code NaN} if any of the values is {@code NaN}. 032 * <li>The result is {@code NaN} if any of the values is negative. 033 * <li>The result is {@code +infinity} if all values are in the range {@code (0, +infinity]} 034 * and at least one value is {@code +infinity}. 035 * <li>The result is {@code 0} if all values are in the range {@code [0, +infinity)} 036 * and at least one value is zero. 037 * <li>The result is {@code NaN} if all values are in the range {@code [0, +infinity]} 038 * and at least one value is zero, and one value is {@code +infinity}. 039 * </ul> 040 * 041 * <p>Supports up to 2<sup>63</sup> (exclusive) observations. 042 * This implementation does not check for overflow of the count. 043 * 044 * <p>This class is designed to work with (though does not require) 045 * {@linkplain java.util.stream streams}. 046 * 047 * <p><strong>This instance is not thread safe.</strong> 048 * If multiple threads access an instance of this class concurrently, 049 * and at least one of the threads invokes the {@link java.util.function.DoubleConsumer#accept(double) accept} or 050 * {@link StatisticAccumulator#combine(StatisticResult) combine} method, it must be synchronized externally. 051 * 052 * <p>However, it is safe to use {@link java.util.function.DoubleConsumer#accept(double) accept} 053 * and {@link StatisticAccumulator#combine(StatisticResult) combine} 054 * as {@code accumulator} and {@code combiner} functions of 055 * {@link java.util.stream.Collector Collector} on a parallel stream, 056 * because the parallel instance of {@link java.util.stream.Stream#collect Stream.collect()} 057 * provides the necessary partitioning, isolation, and merging of results for 058 * safe and efficient parallel execution. 059 * 060 * @see <a href="https://en.wikipedia.org/wiki/Geometric_mean">Geometric mean (Wikipedia)</a> 061 * @see SumOfLogs 062 * @since 1.1 063 */ 064public final class GeometricMean implements DoubleStatistic, StatisticAccumulator<GeometricMean> { 065 /** Count of values that have been added. */ 066 private long n; 067 068 /** 069 * Sum of logs used to compute the geometric mean. 070 */ 071 private final SumOfLogs sumOfLogs; 072 073 /** 074 * Create an instance. 075 */ 076 private GeometricMean() { 077 this(SumOfLogs.create(), 0); 078 } 079 080 /** 081 * Create an instance. 082 * 083 * @param sumOfLogs Sum of logs. 084 * @param n Count of values. 085 */ 086 private GeometricMean(SumOfLogs sumOfLogs, long n) { 087 this.sumOfLogs = sumOfLogs; 088 this.n = n; 089 } 090 091 /** 092 * Creates an instance. 093 * 094 * <p>The initial result is {@code NaN}. 095 * 096 * @return {@code GeometricMean} instance. 097 */ 098 public static GeometricMean create() { 099 return new GeometricMean(); 100 } 101 102 /** 103 * Returns an instance populated using the input {@code values}. 104 * 105 * <p>When the input is an empty array, the result is {@code NaN}. 106 * 107 * @param values Values. 108 * @return {@code GeometricMean} instance. 109 */ 110 public static GeometricMean of(double... values) { 111 return new GeometricMean(SumOfLogs.of(values), values.length); 112 } 113 114 /** 115 * Returns an instance populated using the input {@code values}. 116 * 117 * <p>When the input is an empty array, the result is {@code NaN}. 118 * 119 * @param values Values. 120 * @return {@code GeometricMean} instance. 121 */ 122 public static GeometricMean of(int... values) { 123 return new GeometricMean(SumOfLogs.of(values), values.length); 124 } 125 126 /** 127 * Returns an instance populated using the input {@code values}. 128 * 129 * <p>When the input is an empty array, the result is {@code NaN}. 130 * 131 * @param values Values. 132 * @return {@code GeometricMean} instance. 133 */ 134 public static GeometricMean of(long... values) { 135 return new GeometricMean(SumOfLogs.of(values), values.length); 136 } 137 138 /** 139 * Updates the state of the statistic to reflect the addition of {@code value}. 140 * 141 * @param value Value. 142 */ 143 @Override 144 public void accept(double value) { 145 n++; 146 sumOfLogs.accept(value); 147 } 148 149 /** 150 * Gets the geometric mean of all input values. 151 * 152 * <p>When no values have been added, the result is {@code NaN}. 153 * 154 * @return geometric mean of all values. 155 */ 156 @Override 157 public double getAsDouble() { 158 return computeGeometricMean(n, sumOfLogs); 159 } 160 161 @Override 162 public GeometricMean combine(GeometricMean other) { 163 n += other.n; 164 sumOfLogs.combine(other.sumOfLogs); 165 return this; 166 } 167 168 /** 169 * Compute the geometric mean. 170 * 171 * @param n Count of values. 172 * @param sumOfLogs Sum of logs. 173 * @return the geometric mean 174 */ 175 static double computeGeometricMean(long n, SumOfLogs sumOfLogs) { 176 return n == 0 ? 177 Double.NaN : 178 Math.exp(sumOfLogs.getAsDouble() / n); 179 } 180}