1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.hipparchus.stat.descriptive.rank;
23
24 import java.io.IOException;
25 import java.io.ObjectInputStream;
26 import java.io.Serializable;
27 import java.text.DecimalFormat;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.List;
33
34 import org.hipparchus.analysis.UnivariateFunction;
35 import org.hipparchus.analysis.interpolation.LinearInterpolator;
36 import org.hipparchus.analysis.interpolation.NevilleInterpolator;
37 import org.hipparchus.analysis.interpolation.UnivariateInterpolator;
38 import org.hipparchus.exception.LocalizedCoreFormats;
39 import org.hipparchus.exception.MathIllegalArgumentException;
40 import org.hipparchus.stat.descriptive.AbstractStorelessUnivariateStatistic;
41 import org.hipparchus.stat.descriptive.StorelessUnivariateStatistic;
42 import org.hipparchus.util.MathArrays;
43 import org.hipparchus.util.MathUtils;
44 import org.hipparchus.util.Precision;
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public class PSquarePercentile extends AbstractStorelessUnivariateStatistic
60 implements StorelessUnivariateStatistic, Serializable {
61
62
63 private static final int PSQUARE_CONSTANT = 5;
64
65
66
67
68
69 private static final double DEFAULT_QUANTILE_DESIRED = 50d;
70
71
72 private static final long serialVersionUID = 20150412L;
73
74
75 private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("00.00");
76
77
78
79
80
81 private final List<Double> initialFive = new FixedCapacityList<>(PSQUARE_CONSTANT);
82
83
84
85
86
87
88 private final double quantile;
89
90
91
92
93
94 private transient double lastObservation;
95
96
97
98
99
100 private PSquareMarkers markers;
101
102
103
104
105 private double pValue = Double.NaN;
106
107
108
109
110 private long countOfObservations;
111
112
113
114
115
116
117
118 public PSquarePercentile(final double p) {
119 if (p > 100 || p < 0) {
120 throw new MathIllegalArgumentException(LocalizedCoreFormats.OUT_OF_RANGE,
121 p, 0, 100);
122 }
123 this.quantile = p / 100d;
124 }
125
126
127
128
129
130 PSquarePercentile() {
131 this(DEFAULT_QUANTILE_DESIRED);
132 }
133
134
135
136
137
138
139
140
141 public PSquarePercentile(PSquarePercentile original) {
142 super();
143
144 this.quantile = original.quantile;
145
146 if (original.markers != null) {
147 this.markers = original.markers.copySelf();
148 }
149
150 this.countOfObservations = original.countOfObservations;
151 this.pValue = original.pValue;
152 this.initialFive.addAll(original.initialFive);
153 }
154
155
156 @Override
157 public int hashCode() {
158 double result = getResult();
159 result = Double.isNaN(result) ? 37 : result;
160 final double markersHash = markers == null ? 0 : markers.hashCode();
161 final double[] toHash = {result, quantile, markersHash, countOfObservations};
162 return Arrays.hashCode(toHash);
163 }
164
165
166
167
168
169
170
171
172
173
174 @Override
175 public boolean equals(Object o) {
176 boolean result = false;
177 if (this == o) {
178 result = true;
179 } else if (o instanceof PSquarePercentile) {
180 PSquarePercentile that = (PSquarePercentile) o;
181 boolean isNotNull = markers != null && that.markers != null;
182 boolean isNull = markers == null && that.markers == null;
183 result = isNotNull ? markers.equals(that.markers) : isNull;
184
185
186 result = result && getN() == that.getN();
187 }
188 return result;
189 }
190
191
192
193
194
195
196
197
198 @Override
199 public void increment(final double observation) {
200
201 countOfObservations++;
202
203
204 this.lastObservation = observation;
205
206
207 if (markers == null) {
208 if (initialFive.add(observation)) {
209 Collections.sort(initialFive);
210 pValue =
211 initialFive
212 .get((int) (quantile * (initialFive.size() - 1)));
213 return;
214 }
215
216 markers = newMarkers(initialFive, quantile);
217 }
218
219 pValue = markers.processDataPoint(observation);
220 }
221
222
223
224
225
226
227
228 @Override
229 public String toString() {
230 synchronized (this) {
231 synchronized (DECIMAL_FORMAT) {
232 if (markers == null) {
233 return String.format("obs=%s pValue=%s",
234 DECIMAL_FORMAT.format(lastObservation),
235 DECIMAL_FORMAT.format(pValue));
236 } else {
237 return String.format("obs=%s markers=%s",
238 DECIMAL_FORMAT.format(lastObservation), markers.toString());
239 }
240 }
241 }
242 }
243
244
245 @Override
246 public long getN() {
247 return countOfObservations;
248 }
249
250
251 @Override
252 public PSquarePercentile copy() {
253 return new PSquarePercentile(this);
254 }
255
256
257
258
259
260
261 public double quantile() {
262 return quantile;
263 }
264
265
266
267
268
269 @Override
270 public void clear() {
271 markers = null;
272 initialFive.clear();
273 countOfObservations = 0L;
274 pValue = Double.NaN;
275 }
276
277
278
279
280 @Override
281 public double getResult() {
282 if (Double.compare(quantile, 1d) == 0) {
283 pValue = maximum();
284 } else if (Double.compare(quantile, 0d) == 0) {
285 pValue = minimum();
286 }
287 return pValue;
288 }
289
290
291
292
293 public double getQuantile() {
294 return quantile;
295 }
296
297
298
299
300 private double maximum() {
301 double val = Double.NaN;
302 if (markers != null) {
303 val = markers.height(PSQUARE_CONSTANT);
304 } else if (!initialFive.isEmpty()) {
305 val = initialFive.get(initialFive.size() - 1);
306 }
307 return val;
308 }
309
310
311
312
313 private double minimum() {
314 double val = Double.NaN;
315 if (markers != null) {
316 val = markers.height(1);
317 } else if (!initialFive.isEmpty()) {
318 val = initialFive.get(0);
319 }
320 return val;
321 }
322
323
324
325
326
327 private static class Markers implements PSquareMarkers, Serializable {
328
329
330
331 private static final long serialVersionUID = 1L;
332
333
334 private static final int LOW = 2;
335
336
337 private static final int HIGH = 4;
338
339
340
341
342
343
344 private final Marker[] markerArray;
345
346
347
348
349
350
351 private Markers(final Marker[] theMarkerArray) {
352 MathUtils.checkNotNull(theMarkerArray);
353 markerArray = theMarkerArray;
354 for (int i = 1; i < PSQUARE_CONSTANT; i++) {
355 markerArray[i].previous(markerArray[i - 1])
356 .next(markerArray[i + 1]).index(i);
357 }
358 markerArray[0].previous(markerArray[0])
359 .next(markerArray[1])
360 .index(0);
361 markerArray[5].previous(markerArray[4])
362 .next(markerArray[5])
363 .index(5);
364 }
365
366
367
368
369
370
371
372 private Markers(final List<Double> initialFive, final double p) {
373 this(createMarkerArray(initialFive, p));
374 }
375
376
377
378
379
380
381
382
383 private static Marker[] createMarkerArray(
384 final List<Double> initialFive, final double p) {
385 final int countObserved =
386 initialFive == null ? -1 : initialFive.size();
387 if (countObserved < PSQUARE_CONSTANT) {
388 throw new MathIllegalArgumentException(
389 LocalizedCoreFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
390 countObserved, PSQUARE_CONSTANT);
391 }
392 Collections.sort(initialFive);
393 return new Marker[] {
394 new Marker(),
395 new Marker(initialFive.get(0), 1, 0, 1),
396 new Marker(initialFive.get(1), 1 + 2 * p, p / 2, 2),
397 new Marker(initialFive.get(2), 1 + 4 * p, p, 3),
398 new Marker(initialFive.get(3), 3 + 2 * p, (1 + p) / 2, 4),
399 new Marker(initialFive.get(4), 5, 1, 5) };
400 }
401
402
403
404
405 @Override
406 public int hashCode() {
407 return Arrays.deepHashCode(markerArray);
408 }
409
410
411
412
413
414
415
416
417 @Override
418 public boolean equals(Object o) {
419 boolean result = false;
420 if (this == o) {
421 result = true;
422 } else if (o instanceof Markers) {
423 Markers that = (Markers) o;
424 result = Arrays.deepEquals(markerArray, that.markerArray);
425 }
426 return result;
427 }
428
429
430
431
432
433
434
435 @Override
436 public double processDataPoint(final double inputDataPoint) {
437
438
439 final int kthCell = findCellAndUpdateMinMax(inputDataPoint);
440
441
442 incrementPositions(1, kthCell + 1, 5);
443
444
445 updateDesiredPositions();
446
447
448 adjustHeightsOfMarkers();
449
450
451 return getPercentileValue();
452 }
453
454
455
456
457
458
459 @Override
460 public double getPercentileValue() {
461 return height(3);
462 }
463
464
465
466
467
468
469
470
471 private int findCellAndUpdateMinMax(final double observation) {
472 if (observation < height(1)) {
473 markerArray[1].markerHeight = observation;
474 return 1;
475 } else if (observation < height(2)) {
476 return 1;
477 } else if (observation < height(3)) {
478 return 2;
479 } else if (observation < height(4)) {
480 return 3;
481 } else if (observation <= height(5)) {
482 return 4;
483 } else {
484 markerArray[5].markerHeight = observation;
485 return 4;
486 }
487 }
488
489
490
491
492 private void adjustHeightsOfMarkers() {
493 for (int i = LOW; i <= HIGH; i++) {
494 estimate(i);
495 }
496 }
497
498
499
500
501 @Override
502 public double estimate(final int index) {
503 MathUtils.checkRangeInclusive(index, LOW, HIGH);
504 return markerArray[index].estimate();
505 }
506
507
508
509
510
511
512
513
514
515 private void incrementPositions(final int d, final int startIndex,
516 final int endIndex) {
517 for (int i = startIndex; i <= endIndex; i++) {
518 markerArray[i].incrementPosition(d);
519 }
520 }
521
522
523
524
525
526 private void updateDesiredPositions() {
527 for (int i = 1; i < markerArray.length; i++) {
528 markerArray[i].updateDesiredPosition();
529 }
530 }
531
532
533
534
535
536
537
538
539 private void readObject(ObjectInputStream anInputStream)
540 throws ClassNotFoundException, IOException {
541
542 anInputStream.defaultReadObject();
543
544 for (int i = 1; i < PSQUARE_CONSTANT; i++) {
545 markerArray[i].previous(markerArray[i - 1]).next(markerArray[i + 1]).index(i);
546 }
547 markerArray[0].previous(markerArray[0]).next(markerArray[1]).index(0);
548 markerArray[5].previous(markerArray[4]).next(markerArray[5]).index(5);
549 }
550
551
552
553
554
555
556
557 @Override
558 public double height(final int markerIndex) {
559 MathUtils.checkRangeInclusive(markerIndex, 1, markerArray.length - 1);
560 return markerArray[markerIndex].markerHeight;
561 }
562
563
564 @Override
565 public Markers copySelf() {
566 return new Markers(new Marker[] {
567 new Marker(),
568 markerArray[1].copySelf(),
569 markerArray[2].copySelf(),
570 markerArray[3].copySelf(),
571 markerArray[4].copySelf(),
572 markerArray[5].copySelf()
573 });
574
575 }
576
577
578
579
580
581
582 @Override
583 public String toString() {
584 return String.format("m1=[%s],m2=[%s],m3=[%s],m4=[%s],m5=[%s]",
585 markerArray[1].toString(), markerArray[2].toString(),
586 markerArray[3].toString(), markerArray[4].toString(),
587 markerArray[5].toString());
588 }
589
590 }
591
592
593
594
595 private static class Marker implements Serializable {
596
597
598
599
600 private static final long serialVersionUID = -3575879478288538431L;
601
602
603
604
605
606 private int index;
607
608
609
610
611
612 private double intMarkerPosition;
613
614
615
616
617
618 private double desiredMarkerPosition;
619
620
621
622
623
624 private double markerHeight;
625
626
627
628
629
630 private double desiredMarkerIncrement;
631
632
633
634
635
636 private transient Marker next;
637
638
639
640
641 private transient Marker previous;
642
643
644
645
646 private final UnivariateInterpolator nonLinear = new NevilleInterpolator();
647
648
649
650
651 private transient UnivariateInterpolator linear = new LinearInterpolator();
652
653
654
655
656 private Marker() {
657 this.next = this.previous = this;
658 }
659
660
661
662
663
664
665
666
667
668 private Marker(double heightOfMarker, double makerPositionDesired,
669 double markerPositionIncrement, double markerPositionNumber) {
670 this();
671 this.markerHeight = heightOfMarker;
672 this.desiredMarkerPosition = makerPositionDesired;
673 this.desiredMarkerIncrement = markerPositionIncrement;
674 this.intMarkerPosition = markerPositionNumber;
675 }
676
677
678
679
680
681
682
683
684 private Marker previous(final Marker previousMarker) {
685 MathUtils.checkNotNull(previousMarker);
686 this.previous = previousMarker;
687 return this;
688 }
689
690
691
692
693
694
695
696
697 private Marker next(final Marker nextMarker) {
698 MathUtils.checkNotNull(nextMarker);
699 this.next = nextMarker;
700 return this;
701 }
702
703
704
705
706
707
708
709 private Marker index(final int indexOfMarker) {
710 this.index = indexOfMarker;
711 return this;
712 }
713
714
715
716
717 private void updateDesiredPosition() {
718 desiredMarkerPosition += desiredMarkerIncrement;
719 }
720
721
722
723
724
725
726 private void incrementPosition(final int d) {
727 intMarkerPosition += d;
728 }
729
730
731
732
733
734
735 private double difference() {
736 return desiredMarkerPosition - intMarkerPosition;
737 }
738
739
740
741
742
743
744 private double estimate() {
745 final double di = difference();
746 final boolean isNextHigher =
747 next.intMarkerPosition - intMarkerPosition > 1;
748 final boolean isPreviousLower =
749 previous.intMarkerPosition - intMarkerPosition < -1;
750
751 if (di >= 1 && isNextHigher || di <= -1 && isPreviousLower) {
752 final int d = di >= 0 ? 1 : -1;
753 final double[] xval = { previous.intMarkerPosition, intMarkerPosition, next.intMarkerPosition };
754 final double[] yval = { previous.markerHeight, markerHeight, next.markerHeight };
755 final double xD = intMarkerPosition + d;
756
757 UnivariateFunction univariateFunction =
758 nonLinear.interpolate(xval, yval);
759 markerHeight = univariateFunction.value(xD);
760
761
762 if (isEstimateBad(yval, markerHeight)) {
763 int delta = xD - xval[1] > 0 ? 1 : -1;
764 final double[] xBad = { xval[1], xval[1 + delta] };
765 final double[] yBad = { yval[1], yval[1 + delta] };
766 MathArrays.sortInPlace(xBad, yBad);
767 univariateFunction = linear.interpolate(xBad, yBad);
768 markerHeight = univariateFunction.value(xD);
769 }
770 incrementPosition(d);
771 }
772 return markerHeight;
773 }
774
775
776
777
778
779
780
781
782
783 private boolean isEstimateBad(final double[] y, final double yD) {
784 return yD <= y[0] || yD >= y[2];
785 }
786
787
788
789
790
791
792
793
794
795 @Override
796 public boolean equals(Object o) {
797 boolean result = false;
798 if (this == o) {
799 result = true;
800 } else if (o instanceof Marker) {
801 Marker that = (Marker) o;
802
803 result = Double.compare(markerHeight, that.markerHeight) == 0;
804 result =
805 result &&
806 Double.compare(intMarkerPosition,
807 that.intMarkerPosition) == 0;
808 result =
809 result &&
810 Double.compare(desiredMarkerPosition,
811 that.desiredMarkerPosition) == 0;
812 result =
813 result &&
814 Double.compare(desiredMarkerIncrement,
815 that.desiredMarkerIncrement) == 0;
816
817 result = result && next.index == that.next.index;
818 result = result && previous.index == that.previous.index;
819 }
820 return result;
821 }
822
823
824 @Override
825 public int hashCode() {
826 return Arrays.hashCode(new double[] {markerHeight, intMarkerPosition,
827 desiredMarkerIncrement, desiredMarkerPosition, previous.index, next.index});
828 }
829
830
831
832
833
834
835
836
837 private void readObject(ObjectInputStream anInstream)
838 throws ClassNotFoundException, IOException {
839 anInstream.defaultReadObject();
840 previous=next=this;
841 linear = new LinearInterpolator();
842 }
843
844
845
846
847 public Marker copySelf() {
848 return new Marker(markerHeight, desiredMarkerPosition, desiredMarkerIncrement, intMarkerPosition);
849 }
850
851
852
853
854 @Override
855 public String toString() {
856 return String.format(
857 "index=%.0f,n=%.0f,np=%.2f,q=%.2f,dn=%.2f,prev=%d,next=%d",
858 (double) index, Precision.round(intMarkerPosition, 0),
859 Precision.round(desiredMarkerPosition, 2),
860 Precision.round(markerHeight, 2),
861 Precision.round(desiredMarkerIncrement, 2), previous.index,
862 next.index);
863 }
864 }
865
866
867
868
869
870
871
872
873 private static class FixedCapacityList<E> extends ArrayList<E> implements Serializable {
874
875
876
877
878 private static final long serialVersionUID = 2283952083075725479L;
879
880
881
882 private final int capacity;
883
884
885
886
887
888
889
890 FixedCapacityList(final int fixedCapacity) {
891 super(fixedCapacity);
892 this.capacity = fixedCapacity;
893 }
894
895
896
897
898
899
900
901
902 @Override
903 public boolean add(final E e) {
904 return size() < capacity && super.add(e);
905 }
906
907
908
909
910
911
912
913
914
915 @Override
916 public boolean addAll(Collection<? extends E> collection) {
917 boolean isCollectionLess =
918 collection != null &&
919 collection.size() + size() <= capacity;
920 return isCollectionLess && super.addAll(collection);
921 }
922
923
924 @Override
925 public boolean equals(final Object other) {
926 return super.equals(other) && capacity == ((FixedCapacityList<?>) other).capacity;
927 }
928
929
930 @Override
931 public int hashCode() {
932 return super.hashCode() + capacity;
933 }
934
935 }
936
937
938
939
940
941
942
943
944 public static PSquareMarkers newMarkers(final List<Double> initialFive, final double p) {
945 return new Markers(initialFive, p);
946 }
947
948
949
950
951
952
953 protected interface PSquareMarkers {
954
955
956
957
958
959 double getPercentileValue();
960
961
962
963
964
965
966 PSquareMarkers copySelf();
967
968
969
970
971
972
973
974
975 double height(int markerIndex);
976
977
978
979
980
981
982
983 double processDataPoint(double inputDataPoint);
984
985
986
987
988
989
990
991
992 double estimate(int index);
993 }
994 }