1 package org.djutils.means;
2
3 import java.util.Collection;
4 import java.util.Iterator;
5 import java.util.Map;
6 import java.util.Map.Entry;
7 import java.util.function.Function;
8
9 import org.djutils.exceptions.Throw;
10
11 /**
12 * Methods and fields common to all implementations of Mean. Mean implements various kinds of mean. For an excellent discussion
13 * on this subject read <a href=
14 * "https://towardsdatascience.com/on-average-youre-using-the-wrong-average-geometric-harmonic-means-in-data-analysis-2a703e21ea0"
15 * >On Average, You’re Using the Wrong Average: Geometric & Harmonic Means in Data Analysis</a>
16 * <p>
17 * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
18 * BSD-style license. See <a href="https://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
19 * <p>
20 * @version $Revision$, $LastChangedDate$, by $Author$, initial version Oct 26, 2018 <br>
21 * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
22 * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
23 * @author <a href="https://www.transport.citg.tudelft.nl">Wouter Schakel</a>
24 * @param <MT> mean type
25 * @param <V> value type
26 * @param <W> weight type
27 */
28 public abstract class AbstractMean<MT, V extends Number, W extends Number>
29 {
30 /** Weighted sum of values. Interpretation varies with the kind of mean. */
31 private double weightedSumOfValues;
32
33 /** Sum of weights. */
34 private double sumOfWeights;
35
36 /**
37 * Constructor.
38 */
39 public AbstractMean()
40 {
41 // Nothing to initialize here; the double fields are created with value 0.0 and the unityWeight is initialized to 1.
42 }
43
44 /**
45 * Returns the weighted mean of accumulated data.
46 * @return double; weighted mean of accumulated data
47 */
48 public abstract double getMean();
49
50 /**
51 * Accumulate some data.
52 * @param value double; the value to add to the <code>weightedSumOfValues</code>
53 * @param weight double; the weight to assign to the <code>value</code>
54 */
55 final void increment(final double value, final double weight)
56 {
57 this.weightedSumOfValues += value;
58 this.sumOfWeights += weight;
59 }
60
61 /**
62 * Returns the weighted sum of available data. Meaning varies per type of mean.
63 * @return double; weighted sum of accumulated data
64 */
65 public final double getSum()
66 {
67 return this.weightedSumOfValues;
68 }
69
70 /**
71 * Returns the sum of the weights.
72 * @return double; sum of the weights
73 */
74 public final double getSumOfWeights()
75 {
76 return this.sumOfWeights;
77 }
78
79 /**
80 * Adds a value with weight.
81 * @param value V; the value
82 * @param weight W; the weight
83 * @return this AbstractMean<MT, V, W>; for method chaining
84 */
85 public final AbstractMean<MT, V, W> add(final V value, final W weight)
86 {
87 return addImpl(value, weight);
88 }
89
90 /**
91 * Adds a value with weight.
92 * @param value V; the value
93 * @param weight Number; the weight
94 * @return this AbstractMean<MT, V, W>; for method chaining
95 */
96 protected abstract AbstractMean<MT, V, W> addImpl(final V value, final Number weight);
97
98 /** Unity weight. */
99 private final Number unityWeight = new Integer(1);
100
101 /**
102 * Add a value with weight 1.
103 * @param value V; the value
104 * @return this AbstractMean<MT, V, W>; for method chaining
105 */
106 public final AbstractMean<MT, V, W> add(final V value)
107 {
108 return addImpl(value, this.unityWeight);
109 }
110
111 /**
112 * Adds weighted values. Note that iteration order is pivotal in correct operations. This method should not be used with
113 * instances of {@code HashMap} or {@code HashSet}.
114 * @param values Iterable<V>; values
115 * @param weights Iterable<W>; weights
116 * @return this AbstractMean<MT, V, W>; for method chaining
117 * @throws IllegalArgumentException if the number of values is not equal to the number of weights
118 */
119 public final AbstractMean<MT, V, W> add(final Iterable<V> values, final Iterable<W> weights) throws IllegalArgumentException
120 {
121 Iterator<V> itV = values.iterator();
122 Iterator<W> itW = weights.iterator();
123 while (itV.hasNext())
124 {
125 Throw.when(!itW.hasNext(), IllegalArgumentException.class, "Unequal number of values and weights.");
126 addImpl(itV.next(), itW.next());
127 }
128 Throw.when(itW.hasNext(), IllegalArgumentException.class, "Unequal number of values and weights.");
129 return this;
130 }
131
132 /**
133 * Adds weighted values.
134 * @param values V[]; values
135 * @param weights W[]; weights
136 * @return this AbstractMean<MT, V, W>; for method chaining
137 * @throws IllegalArgumentException if the number of values is not equal to the number of weights
138 */
139 public final AbstractMean<MT, V, W> add(final V[] values, final W[] weights) throws IllegalArgumentException
140 {
141 Throw.when(values.length != weights.length, IllegalArgumentException.class, "Unequal number of values and weights.");
142 for (int i = 0; i < values.length; i++)
143 {
144 addImpl(values[i], weights[i]);
145 }
146 return this;
147 }
148
149 /**
150 * Adds each key value from a map weighted with the mapped to value.
151 * @param map Map<V, W>; map
152 * @return this AbstractMean<MT, V, W>; for method chaining
153 */
154 public final AbstractMean<MT, V, W> add(final Map<V, W> map)
155 {
156 for (Entry<V, W> entry : map.entrySet())
157 {
158 addImpl(entry.getKey(), entry.getValue());
159 }
160 return this;
161 }
162
163 /**
164 * Adds each value with a weight obtained by calling the provided <code>weights</code> function.
165 * @param collection Collection<V>; values
166 * @param weights Function<V, W>; weights
167 * @return this AbstractMean<MT, V, W>; for method chaining
168 */
169 public final AbstractMean<MT, V, W> add(final Collection<V> collection, final Function<V, W> weights)
170 {
171 for (V v : collection)
172 {
173 addImpl(v, weights.apply(v));
174 }
175 return this;
176 }
177
178 /**
179 * Adds each value (obtained by calling the <code>values</code> function on each object in a Collection) with a weight
180 * (obtained by calling the <code> weights</code> function on the same object from the Collection).
181 * @param collection Collection<V>; collection of source objects
182 * @param values Function<V, W>; values
183 * @param weights Function<V, W>; weights
184 * @param <S> type of source object
185 * @return this AbstractMean<MT, V, W>; for method chaining
186 */
187 public final <S> AbstractMean<MT, V, W> add(final Collection<S> collection, final Function<S, V> values,
188 final Function<S, W> weights)
189 {
190 for (S s : collection)
191 {
192 addImpl(values.apply(s), weights.apply(s));
193 }
194 return this;
195 }
196
197 /**
198 * Add values with weight 1.
199 * @param values V[]; the values to add
200 * @return this AbstractMean<MT, V, W>; for method chaining
201 */
202 public final AbstractMean<MT, V, W> add(final Iterable<V> values)
203 {
204 Iterator<V> itV = values.iterator();
205 while (itV.hasNext())
206 {
207 addImpl(itV.next(), this.unityWeight);
208 }
209 return this;
210 }
211
212 /**
213 * Add values with weight 1.
214 * @param values V[]; the values to add
215 * @return this AbstractMean<MT, V, W>; for method chaining
216 */
217 public final AbstractMean<MT, V, W> add(final V[] values)
218 {
219 for (int i = 0; i < values.length; i++)
220 {
221 addImpl(values[i], this.unityWeight);
222 }
223 return this;
224 }
225
226 }