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 @Override
121 public double register(final double value)
122 {
123 Throw.when(Double.isNaN(value), IllegalArgumentException.class, "accumulator can not accumlate NaN value");
124 this.cumulatives = null;
125 double floatBin = (value - this.minimumBinCenter) / binWidth;
126 int bin = (int) Math.rint(floatBin);
127 if (bin < 0)
128 {
129 this.belowCount++;
130 }
131 else if (bin >= this.accumulator.length)
132 {
133 this.aboveCount++;
134 }
135 else
136 {
137 this.accumulator[bin]++;
138 }
139 this.totalCount++;
140 return value;
141 }
142
143
144
145
146 private void ensureCumulatives()
147 {
148 if (null == this.cumulatives)
149 {
150 long count = 0;
151 this.cumulatives = new long[this.accumulator.length];
152 for (int bin = 0; bin < this.accumulator.length; bin++)
153 {
154 count += this.accumulator[bin];
155 this.cumulatives[bin] = count;
156 }
157 }
158 }
159
160 @Override
161 public double getQuantile(final Tally tally, final double probability)
162 {
163 Throw.when(!Double.isFinite(probability) || probability < 0.0 || probability > 1.0, IllegalArgumentException.class,
164 "probability must be a value between 0 and 1");
165 ensureCumulatives();
166
167 long usableCount = this.totalCount - this.belowCount - this.aboveCount;
168 if (usableCount == 0)
169 {
170 return Double.NaN;
171 }
172 double value = (usableCount) * probability;
173
174 for (int bin = 0; bin < this.cumulatives.length; bin++)
175 {
176 if (this.cumulatives[bin] >= value)
177 {
178 return bin * this.binWidth + this.minimumBinCenter;
179 }
180 }
181 return 0;
182 }
183
184 @Override
185 public double getCumulativeProbability(final Tally tally, final double quantile) throws IllegalArgumentException
186 {
187 Throw.when(Double.isNaN(quantile), IllegalArgumentException.class, "quantile may not be NaN");
188
189 if (this.totalCount == 0)
190 {
191 return Double.NaN;
192 }
193 double floatBin = (quantile - this.minimumBinCenter) / binWidth;
194 int bin = (int) Math.rint(floatBin);
195 if (bin < 0)
196 {
197 return 0.0;
198 }
199 if (bin >= this.accumulator.length)
200 {
201 return 1.0;
202 }
203 ensureCumulatives();
204 return 1.0 * this.cumulatives[bin] / this.totalCount + (floatBin - bin - 0.5) * this.accumulator[bin] / this.totalCount;
205 }
206
207 @Override
208 public void initialize()
209 {
210 this.belowCount = 0;
211 this.aboveCount = 0;
212 this.accumulator = new long[this.accumulator.length];
213 this.cumulatives = null;
214 this.totalCount = 0;
215 }
216
217 @Override
218 public String toString()
219 {
220 return "FixedBinsAccumulator [minimumBinCenter=" + minimumBinCenter + ", binWidth=" + binWidth + ", totalCount="
221 + totalCount + ", belowCount=" + belowCount + ", aboveCount=" + aboveCount + "]";
222 }
223
224 }