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 @Override
253 public final double getSampleKurtosis()
254 {
255 if (this.n > 3)
256 {
257 double sVar = getSampleVariance();
258 return this.m4 / (this.n - 1) / sVar / sVar;
259 }
260 return Double.NaN;
261 }
262
263
264 @Override
265 public final double getPopulationKurtosis()
266 {
267 if (this.n > 2)
268 {
269 return (this.m4 / this.n) / (this.m2 / this.n) / (this.m2 / this.n);
270 }
271 return Double.NaN;
272 }
273
274
275 @Override
276 public final double getSampleExcessKurtosis()
277 {
278 if (this.n > 3)
279 {
280 double g2 = getPopulationExcessKurtosis();
281 return (1.0 * (this.n - 1) / (this.n - 2) / (this.n - 3)) * ((this.n + 1) * g2 + 6.0);
282 }
283 return Double.NaN;
284 }
285
286
287 @Override
288 public final double getPopulationExcessKurtosis()
289 {
290 if (this.n > 2)
291 {
292
293 return getPopulationKurtosis() - 3.0;
294 }
295 return Double.NaN;
296 }
297
298
299 @Override
300 public void initialize()
301 {
302 synchronized (this.semaphore)
303 {
304 this.min = Double.NaN;
305 this.max = Double.NaN;
306 this.n = 0;
307 this.sum = 0.0;
308 this.m1 = 0.0;
309 this.m2 = 0.0;
310 this.m3 = 0;
311 this.m4 = 0;
312 this.quantileAccumulator.initialize();
313 }
314 }
315
316
317
318
319
320 public void ingest(final double... values)
321 {
322 for (double value : values)
323 {
324 ingest(value);
325 }
326 }
327
328
329 @Override
330 public double ingest(final double value)
331 {
332 Throw.when(Double.isNaN(value), IllegalArgumentException.class, "value may not be NaN");
333 synchronized (this.semaphore)
334 {
335 if (this.n == 0)
336 {
337 this.min = Double.MAX_VALUE;
338 this.max = -Double.MAX_VALUE;
339 }
340 this.n++;
341 double delta = value - this.m1;
342 double oldm2 = this.m2;
343 double oldm3 = this.m3;
344
345
346 this.m1 += delta / this.n;
347
348
349 this.m2 += delta * (value - this.m1);
350
351 this.m3 += -3 * oldm2 * delta / this.n + (this.n - 1) * (this.n - 2) * delta * delta * delta / this.n / this.n;
352
353 this.m4 += -4 * oldm3 * delta / this.n + 6 * oldm2 * delta * delta / this.n / this.n + (this.n - 1)
354 * (this.n * this.n - 3 * this.n + 3) * delta * delta * delta * delta / this.n / this.n / this.n;
355 this.sum += value;
356 if (value < this.min)
357 {
358 this.min = value;
359 }
360 if (value > this.max)
361 {
362 this.max = value;
363 }
364 this.quantileAccumulator.ingest(value);
365 }
366 return value;
367 }
368
369
370 @Override
371 @SuppressWarnings("checkstyle:designforextension")
372 public String toString()
373 {
374 return "Tally [sum=" + this.sum + ", m1=" + this.m1 + ", m2=" + this.m2 + ", m3=" + this.m3 + ", m4=" + this.m4
375 + ", min=" + this.min + ", max=" + this.max + ", n=" + this.n + ", description=" + this.description
376 + ", quantileAccumulator=" + this.quantileAccumulator + "]";
377 }
378
379 }