1 package org.djutils.stats.summarizers.quantileaccumulator;
2
3 import org.djutils.exceptions.Throw;
4 import org.djutils.stats.summarizers.Tally;
5
6
7
8
9
10
11
12
13
14
15
16
17 public class FixedBinsAccumulator implements QuantileAccumulator
18 {
19
20 private final double minimumBinCenter;
21
22
23 private final double binWidth;
24
25
26 private long[] accumulator;
27
28
29 private long[] cumulatives = null;
30
31
32 private long totalCount = 0;
33
34
35 private long belowCount = 0;
36
37
38 private long aboveCount = 0;
39
40
41
42
43
44
45
46
47
48 public FixedBinsAccumulator(final double minimumBinCenter, final double binWidth, final int binCount)
49 {
50 Throw.when(!Double.isFinite(minimumBinCenter), IllegalArgumentException.class, "minimumBinCenter must be finite");
51 Throw.when(!Double.isFinite(binWidth), IllegalArgumentException.class, "binWidth must be finite");
52 Throw.when(binWidth <= 0, IllegalArgumentException.class, "binWidth must be positive");
53 Throw.when(binCount < 1, IllegalArgumentException.class, "binCount must be > 0");
54 this.minimumBinCenter = minimumBinCenter;
55 this.binWidth = binWidth;
56 this.belowCount = 0;
57 this.aboveCount = 0;
58 this.accumulator = new long[binCount];
59 this.cumulatives = null;
60 this.totalCount = 0;
61 }
62
63
64
65
66
67 public double getBinWidth()
68 {
69 return this.binWidth;
70 }
71
72
73
74
75
76 public int getBinCount()
77 {
78 return this.accumulator.length;
79 }
80
81
82
83
84
85 public long getN()
86 {
87 return this.totalCount;
88 }
89
90
91
92
93
94 public long getBelowCount()
95 {
96 return this.belowCount;
97 }
98
99
100
101
102
103 public long getAboveCount()
104 {
105 return this.aboveCount;
106 }
107
108
109
110
111
112
113 public double getBinCenter(final int bin)
114 {
115 Throw.when(bin < 0 || bin >= this.accumulator.length, IllegalArgumentException.class,
116 "bin must be in range 0..$1; got $2", this.accumulator.length - 1, bin);
117 return this.minimumBinCenter + bin * this.binWidth;
118 }
119
120
121 @Override
122 public double register(final double value)
123 {
124 Throw.when(Double.isNaN(value), IllegalArgumentException.class, "accumulator can not accumlate NaN value");
125 this.cumulatives = null;
126 double floatBin = (value - this.minimumBinCenter) / binWidth;
127 int bin = (int) Math.rint(floatBin);
128 if (bin < 0)
129 {
130 this.belowCount++;
131 }
132 else if (bin >= this.accumulator.length)
133 {
134 this.aboveCount++;
135 }
136 else
137 {
138 this.accumulator[bin]++;
139 }
140 this.totalCount++;
141 return value;
142 }
143
144
145
146
147 private void ensureCumulatives()
148 {
149 if (null == this.cumulatives)
150 {
151 long count = 0;
152 this.cumulatives = new long[this.accumulator.length];
153 for (int bin = 0; bin < this.accumulator.length; bin++)
154 {
155 count += this.accumulator[bin];
156 this.cumulatives[bin] = count;
157 }
158 }
159 }
160
161
162 @Override
163 public double getQuantile(final Tally tally, final double probability)
164 {
165 Throw.when(!Double.isFinite(probability) || probability < 0.0 || probability > 1.0, IllegalArgumentException.class,
166 "probability must be a value between 0 and 1");
167 ensureCumulatives();
168
169 long usableCount = this.totalCount - this.belowCount - this.aboveCount;
170 if (usableCount == 0)
171 {
172 return Double.NaN;
173 }
174 double value = (usableCount) * probability;
175
176 for (int bin = 0; bin < this.cumulatives.length; bin++)
177 {
178 if (this.cumulatives[bin] >= value)
179 {
180 return bin * this.binWidth + this.minimumBinCenter;
181 }
182 }
183 return 0;
184 }
185
186
187 @Override
188 public double getCumulativeProbability(final Tally tally, final double quantile) throws IllegalArgumentException
189 {
190 Throw.when(Double.isNaN(quantile), IllegalArgumentException.class, "quantile may not be NaN");
191
192 if (this.totalCount == 0)
193 {
194 return Double.NaN;
195 }
196 double floatBin = (quantile - this.minimumBinCenter) / binWidth;
197 int bin = (int) Math.rint(floatBin);
198 if (bin < 0)
199 {
200 return 0.0;
201 }
202 if (bin >= this.accumulator.length)
203 {
204 return 1.0;
205 }
206 ensureCumulatives();
207 return 1.0 * this.cumulatives[bin] / this.totalCount + (floatBin - bin - 0.5) * this.accumulator[bin] / this.totalCount;
208 }
209
210
211 @Override
212 public void initialize()
213 {
214 this.belowCount = 0;
215 this.aboveCount = 0;
216 this.accumulator = new long[this.accumulator.length];
217 this.cumulatives = null;
218 this.totalCount = 0;
219 }
220
221
222 @Override
223 public String toString()
224 {
225 return "FixedBinsAccumulator [minimumBinCenter=" + minimumBinCenter + ", binWidth=" + binWidth + ", totalCount="
226 + totalCount + ", belowCount=" + belowCount + ", aboveCount=" + aboveCount + "]";
227 }
228
229 }