1 package org.djutils.stats.summarizers;
2
3 import org.djutils.exceptions.Throw;
4 import org.djutils.stats.ConfidenceInterval;
5 import org.djutils.stats.DistNormalTable;
6 import org.djutils.stats.summarizers.quantileaccumulator.NoStorageAccumulator;
7 import org.djutils.stats.summarizers.quantileaccumulator.QuantileAccumulator;
8
9
10
11
12
13
14
15
16
17
18
19
20
21 public class Tally implements TallyInterface
22 {
23
24 private static final long serialVersionUID = 20200228L;
25
26
27 private double sum = 0;
28
29
30 private double m1 = 0;
31
32
33 private double m2 = 0;
34
35
36 private double m3 = 0;
37
38
39 private double m4 = 0;
40
41
42 private double min = Double.NaN;
43
44
45 private double max = Double.NaN;
46
47
48 private long n = 0;
49
50
51 private final String description;
52
53
54 private final QuantileAccumulator quantileAccumulator;
55
56
57 @SuppressWarnings("checkstyle:visibilitymodifier")
58 protected Object semaphore = new Object();
59
60
61
62
63
64
65 public Tally(final String description, final QuantileAccumulator quantileAccumulator)
66 {
67 this.description = description;
68 this.quantileAccumulator = quantileAccumulator;
69 initialize();
70 }
71
72
73
74
75
76 public Tally(final String description)
77 {
78 this(description, new NoStorageAccumulator());
79 }
80
81
82 @Override
83 public final double getSampleMean()
84 {
85 if (this.n > 0)
86 {
87 return this.m1;
88 }
89 return Double.NaN;
90 }
91
92
93 @Override
94 public final double getQuantile(final double probability)
95 {
96 return this.quantileAccumulator.getQuantile(this, probability);
97 }
98
99
100 @Override
101 public final double[] getConfidenceInterval(final double alpha)
102 {
103 return this.getConfidenceInterval(alpha, ConfidenceInterval.BOTH_SIDE_CONFIDENCE);
104 }
105
106
107 @Override
108 public final double[] getConfidenceInterval(final double alpha, final ConfidenceInterval side)
109 {
110 Throw.whenNull(side, "type of confidence level cannot be null");
111 Throw.when(alpha < 0 || alpha > 1, IllegalArgumentException.class,
112 "confidenceLevel should be between 0 and 1 (inclusive)");
113 synchronized (this.semaphore)
114 {
115 double sampleMean = getSampleMean();
116 if (Double.isNaN(sampleMean) || Double.valueOf(this.getSampleStDev()).isNaN())
117 {
118 return null;
119 }
120 double level = 1 - alpha;
121 if (side.equals(ConfidenceInterval.BOTH_SIDE_CONFIDENCE))
122 {
123 level = 1 - alpha / 2.0;
124 }
125 double z = DistNormalTable.getInverseCumulativeProbability(0.0, 1.0, level);
126 double confidence = z * Math.sqrt(this.getSampleVariance() / this.n);
127 double[] result = {sampleMean - confidence, sampleMean + confidence};
128 if (side.equals(ConfidenceInterval.LEFT_SIDE_CONFIDENCE))
129 {
130 result[1] = sampleMean;
131 }
132 if (side.equals(ConfidenceInterval.RIGHT_SIDE_CONFIDENCE))
133 {
134 result[0] = sampleMean;
135 }
136 result[0] = Math.max(result[0], this.min);
137 result[1] = Math.min(result[1], this.max);
138 return result;
139 }
140 }
141
142
143 @Override
144 public final String getDescription()
145 {
146 return this.description;
147 }
148
149
150 @Override
151 public final double getMax()
152 {
153 return this.max;
154 }
155
156
157 @Override
158 public final double getMin()
159 {
160 return this.min;
161 }
162
163
164 @Override
165 public final long getN()
166 {
167 return this.n;
168 }
169
170
171 @Override
172 public final double getSampleStDev()
173 {
174 synchronized (this.semaphore)
175 {
176 if (this.n > 1)
177 {
178 return Math.sqrt(getSampleVariance());
179 }
180 return Double.NaN;
181 }
182 }
183
184
185 @Override
186 public final double getPopulationStDev()
187 {
188 synchronized (this.semaphore)
189 {
190 return Math.sqrt(getPopulationVariance());
191 }
192 }
193
194
195 @Override
196 public final double getSum()
197 {
198 return this.sum;
199 }
200
201
202 @Override
203 public final double getSampleVariance()
204 {
205 synchronized (this.semaphore)
206 {
207 if (this.n > 1)
208 {
209 return this.m2 / (this.n - 1);
210 }
211 return Double.NaN;
212 }
213 }
214
215
216 @Override
217 public final double getPopulationVariance()
218 {
219 synchronized (this.semaphore)
220 {
221 if (this.n > 0)
222 {
223 return this.m2 / this.n;
224 }
225 return Double.NaN;
226 }
227 }
228
229
230 @Override
231 public final double getSampleSkewness()
232 {
233 if (this.n > 2)
234 {
235 return getPopulationSkewness() * Math.sqrt(this.n * (this.n - 1)) / (this.n - 2);
236 }
237 return Double.NaN;
238 }
239
240
241 @Override
242 public final double getPopulationSkewness()
243 {
244 if (this.n > 1)
245 {
246 return (this.m3 / this.n) / Math.pow(getPopulationVariance(), 1.5);
247 }
248 return Double.NaN;
249 }
250
251
252
253 @Override
254 public final double getSampleKurtosis()
255 {
256 if (this.n > 3)
257 {
258 double sVar = getSampleVariance();
259 return this.m4 / (this.n - 1) / sVar / sVar;
260 }
261 return Double.NaN;
262 }
263
264
265 @Override
266 public final double getPopulationKurtosis()
267 {
268 if (this.n > 2)
269 {
270 return (this.m4 / this.n) / (this.m2 / this.n) / (this.m2 / this.n);
271 }
272 return Double.NaN;
273 }
274
275
276 @Override
277 public final double getSampleExcessKurtosis()
278 {
279 if (this.n > 3)
280 {
281 double g2 = getPopulationExcessKurtosis();
282 return (1.0 * (this.n - 1) / (this.n - 2) / (this.n - 3)) * ((this.n + 1) * g2 + 6.0);
283 }
284 return Double.NaN;
285 }
286
287
288 @Override
289 public final double getPopulationExcessKurtosis()
290 {
291 if (this.n > 2)
292 {
293
294 return getPopulationKurtosis() - 3.0;
295 }
296 return Double.NaN;
297 }
298
299
300 @Override
301 public void initialize()
302 {
303 synchronized (this.semaphore)
304 {
305 this.min = Double.NaN;
306 this.max = Double.NaN;
307 this.n = 0;
308 this.sum = 0.0;
309 this.m1 = 0.0;
310 this.m2 = 0.0;
311 this.m3 = 0;
312 this.m4 = 0;
313 this.quantileAccumulator.initialize();
314 }
315 }
316
317
318
319
320
321 public void ingest(final double... values)
322 {
323 for (double value : values)
324 {
325 ingest(value);
326 }
327 }
328
329
330 @Override
331 public double ingest(final double value)
332 {
333 Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
334 synchronized (this.semaphore)
335 {
336 if (this.n == 0)
337 {
338 this.min = Double.MAX_VALUE;
339 this.max = -Double.MAX_VALUE;
340 }
341 this.n++;
342 double delta = value - this.m1;
343 double oldm2 = this.m2;
344 double oldm3 = this.m3;
345
346
347 this.m1 += delta / this.n;
348
349
350 this.m2 += delta * (value - this.m1);
351
352 this.m3 += -3 * oldm2 * delta / this.n + (this.n - 1) * (this.n - 2) * delta * delta * delta / this.n / this.n;
353
354 this.m4 += -4 * oldm3 * delta / this.n + 6 * oldm2 * delta * delta / this.n / this.n + (this.n - 1)
355 * (this.n * this.n - 3 * this.n + 3) * delta * delta * delta * delta / this.n / this.n / this.n;
356 this.sum += value;
357 if (value < this.min)
358 {
359 this.min = value;
360 }
361 if (value > this.max)
362 {
363 this.max = value;
364 }
365 this.quantileAccumulator.ingest(value);
366 }
367 return value;
368 }
369
370
371 @Override
372 @SuppressWarnings("checkstyle:designforextension")
373 public String toString()
374 {
375 return "Tally [sum=" + this.sum + ", m1=" + this.m1 + ", m2=" + this.m2 + ", m3=" + this.m3 + ", m4=" + this.m4
376 + ", min=" + this.min + ", max=" + this.max + ", n=" + this.n + ", description=" + this.description
377 + ", quantileAccumulator=" + this.quantileAccumulator + "]";
378 }
379
380 }