/*
GeoGebra - Dynamic Mathematics for Everyone
http://www.geogebra.org
This file is part of GeoGebra.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation.
*/
package org.geogebra.common.kernel.algos;
import java.util.ArrayList;
import org.apache.commons.math3.util.Cloner;
import org.geogebra.common.kernel.Construction;
import org.geogebra.common.kernel.arithmetic.NumberValue;
import org.geogebra.common.kernel.commands.Commands;
import org.geogebra.common.kernel.geos.GeoBoolean;
import org.geogebra.common.kernel.geos.GeoElement;
import org.geogebra.common.kernel.geos.GeoList;
import org.geogebra.common.kernel.geos.GeoNumberValue;
import org.geogebra.common.kernel.geos.GeoNumeric;
/**
* Boxplot algorithm. See AlgoFunctionAreaSums for implementation.
*
* @author George Sturr
*
*/
public class AlgoBoxPlot extends AlgoElement implements DrawInformationAlgo {
private static final int TYPE_QUARTILES = 0;
private static final int TYPE_RAW = 1;
private static final int TYPE_FREQUENCY = 2;
private int type;
private GeoNumberValue a;
private GeoNumberValue b;
private GeoElement ageo;
private GeoElement bgeo;
private GeoElement minGeo;
private GeoElement Q1geo;
private GeoElement medianGeo;
private GeoElement Q3geo;
private GeoElement maxGeo;
private GeoNumeric sum;
private GeoBoolean useOutliersGeo;
private GeoList list1;
private GeoList freqList;
private ArrayList<Double> tempList;
private int N;
private double[] yval;
private double[] leftBorder;
/**
* Creates boxplot given all the quartiles, y-offset and y-scale
*
* @param cons
* construction
* @param label
* label
* @param a
* y-offset
* @param b
* y-scale
* @param min
* @param Q1
* @param median
* @param Q3
* @param max
*/
public AlgoBoxPlot(Construction cons, String label, GeoNumberValue a,
GeoNumberValue b, GeoNumberValue min, GeoNumberValue Q1,
GeoNumberValue median, GeoNumberValue Q3, GeoNumberValue max) {
super(cons);
type = TYPE_QUARTILES;
this.a = a;
this.b = b;
ageo = a.toGeoElement();
bgeo = b.toGeoElement();
minGeo = min.toGeoElement();
Q1geo = Q1.toGeoElement();
medianGeo = median.toGeoElement();
Q3geo = Q3.toGeoElement();
maxGeo = max.toGeoElement();
sum = new GeoNumeric(cons); // output
// sum.setLabelVisible(false);
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
sum.setLabel(label);
}
/**
* Creates boxplot from list of raw data
*
* @param cons
* construction
* @param label
* label
* @param a
* y-offset
* @param b
* y-scale
* @param list1
* rawData
* @param useOutliers
* whether to plot outliers separately
*/
public AlgoBoxPlot(Construction cons, String label, GeoNumberValue a,
GeoNumberValue b, GeoList list1, GeoBoolean useOutliers) {
this(cons, a, b, list1, useOutliers);
sum.setLabel(label);
}
public AlgoBoxPlot(Construction cons, String label, GeoNumberValue a,
GeoNumberValue b, GeoList list1, GeoList freqList,
GeoBoolean useOutliers) {
this(cons, a, b, list1, freqList, useOutliers);
sum.setLabel(label);
}
/**
* Creates boxplot from frequency table
*
* @param cons
* construction
* @param a
* y-offset
* @param b
* y-scale
* @param list1
* rawData
* @param useOutliers
* whether to plot outliers separately
*/
public AlgoBoxPlot(Construction cons, GeoNumberValue a, GeoNumberValue b,
GeoList list1, GeoList freqList, GeoBoolean useOutliers) {
super(cons);
type = TYPE_FREQUENCY;
this.a = a;
this.b = b;
ageo = a.toGeoElement();
bgeo = b.toGeoElement();
this.list1 = list1;
this.freqList = freqList;
this.useOutliersGeo = useOutliers;
sum = new GeoNumeric(cons); // output
// sum.setLabelVisible(false);
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
}
public AlgoBoxPlot(Construction cons, GeoNumberValue a, GeoNumberValue b,
GeoList list1, GeoBoolean useOutliers) {
super(cons);
type = TYPE_RAW;
this.a = a;
this.b = b;
ageo = a.toGeoElement();
bgeo = b.toGeoElement();
this.list1 = list1;
this.useOutliersGeo = useOutliers;
sum = new GeoNumeric(cons); // output
// sum.setLabelVisible(false);
setInputOutput(); // for AlgoElement
compute();
sum.setDrawable(true);
}
private AlgoBoxPlot(Construction cons, double[] list1, GeoNumberValue a,
GeoNumberValue b) {
super(cons, false);
type = TYPE_RAW;
this.a = a;
this.b = b;
this.leftBorder = list1;
}
public NumberValue getB() {
return b;
}
public NumberValue getA() {
return a;
}
public GeoList getList1() {
return list1;
}
@Override
public Commands getClassName() {
return Commands.BoxPlot;
}
@Override
public AlgoBoxPlot copy() {
return new AlgoBoxPlot(cons, Cloner.clone(leftBorder),
(GeoNumberValue) a.deepCopy(kernel),
(GeoNumberValue) b.deepCopy(kernel));
}
@Override
public void compute() {
boolean useOutliers = false;
if (useOutliersGeo != null && useOutliersGeo.getBoolean()) {
useOutliers = true;
}
outliers = null;
if (tempList == null) {
tempList = new ArrayList<Double>();
}
tempList.clear();
if (type == TYPE_FREQUENCY) {
if (list1.size() == 0 || list1.size() != freqList.size()) {
sum.setUndefined();
return;
}
}
if (type == TYPE_RAW || type == TYPE_FREQUENCY) {
AlgoQ1 Q1Algo;
AlgoQ3 Q3Algo;
AlgoMedian medianAlgo;
if (type == TYPE_RAW) {
Q1Algo = new AlgoQ1(cons, list1);
medianAlgo = new AlgoMedian(cons, list1);
Q3Algo = new AlgoQ3(cons, list1);
} else {
Q1Algo = new AlgoQ1(cons, list1, freqList);
medianAlgo = new AlgoMedian(cons, list1, freqList);
Q3Algo = new AlgoQ3(cons, list1, freqList);
}
cons.removeFromConstructionList(Q1Algo);
cons.removeFromConstructionList(Q3Algo);
cons.removeFromConstructionList(medianAlgo);
double median = medianAlgo.getMedian().getDouble();
double Q1 = Q1Algo.getQ1().getDouble();
double Q3 = Q3Algo.getQ3().getDouble();
double min = Double.MAX_VALUE;
double max = -Double.MAX_VALUE;
for (int i = 0; i < list1.size(); i++) {
double x = list1.get(i).evaluateDouble();
if (type == TYPE_FREQUENCY
&& ((GeoNumeric) freqList.get(i)).getDouble() <= 0) {
continue;
}
boolean updateMaxMin = true;
if (useOutliers) {
// outlier = more than 1.5 * IQR above Q3...
if (x > Q3 + 1.5 * (Q3 - Q1)) {
addOutlier(x);
updateMaxMin = false;
}
// ...or less then 1.5 * IQR below Q1
if (x < Q1 - 1.5 * (Q3 - Q1)) {
addOutlier(x);
updateMaxMin = false;
}
}
// need to adjust max/min (ie exclude outliers)
if (updateMaxMin) {
if (x < min) {
min = x;
}
// no else (think!)
if (x > max) {
max = x;
}
}
}
// Log.debug(min+" "+Q1+" "+median+" "+Q3+" "+max);
tempList.add(min);
tempList.add(Q1);
tempList.add(median);
tempList.add(Q3);
tempList.add(max);
N = 5;
calcBoxPlot();
}
else {// TYPE_QUARTILES:
tempList.add(minGeo.evaluateDouble());
tempList.add(Q1geo.evaluateDouble());
tempList.add(medianGeo.evaluateDouble());
tempList.add(Q3geo.evaluateDouble());
tempList.add(maxGeo.evaluateDouble());
N = 5;
calcBoxPlot();
}
}
private void calcBoxPlot() {
if (yval == null || yval.length < N) {
yval = new double[N];
leftBorder = new double[N];
}
for (int i = 0; i < N; i++) {
double x = tempList.get(i);
if (!Double.isNaN(x)) {
leftBorder[i] = x;
} else {
sum.setUndefined();
return;
}
yval[i] = 1.0; // dummy value
}
sum.setValue(leftBorder[2]); // median
}
@Override
protected void setInputOutput() {
switch (type) {
default:
// do nothing
break;
case TYPE_QUARTILES:
input = new GeoElement[7];
input[0] = ageo;
input[1] = bgeo;
input[2] = minGeo;
input[3] = Q1geo;
input[4] = medianGeo;
input[5] = Q3geo;
input[6] = maxGeo;
break;
case TYPE_RAW:
input = new GeoElement[3 + (useOutliersGeo == null ? 0 : 1)];
input[0] = ageo;
input[1] = bgeo;
input[2] = list1;
if (useOutliersGeo != null) {
input[3] = useOutliersGeo;
}
break;
case TYPE_FREQUENCY:
input = new GeoElement[4 + (useOutliersGeo == null ? 0 : 1)];
input[0] = ageo;
input[1] = bgeo;
input[2] = list1;
input[3] = freqList;
if (useOutliersGeo != null) {
input[4] = useOutliersGeo;
}
break;
}
setOutputLength(1);
setOutput(0, sum);
setDependencies();
}
public GeoNumeric getSum() {
return sum;
}
/**
* Returns minimum
*
* @return minimum
*/
public GeoElement getMinGeo() {
return minGeo;
}
/**
* Returns maximum
*
* @return maximum
*/
public GeoElement getMaxGeo() {
return maxGeo;
}
/**
* Returns Q1
*
* @return Q1
*/
public GeoElement getQ1geo() {
return Q1geo;
}
/**
* Returns Q3
*
* @return Q3
*/
public GeoElement getQ3geo() {
return Q3geo;
}
/**
* Returns median
*
* @return median
*/
public GeoElement getMedianGeo() {
return medianGeo;
}
public double[] getLeftBorders() {
return leftBorder;
}
private ArrayList<Double> outliers;
public ArrayList<Double> getOutliers() {
return outliers;
}
private void addOutlier(double x) {
// Log.debug("outlier "+x);
if (outliers == null) {
outliers = new ArrayList<Double>();
}
outliers.add(x);
}
}