Skip to content

Commit

Permalink
Implemented GEOMEAN function. Thanks to gallonfizik. This closes apac…
Browse files Browse the repository at this point in the history
…he#136

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1849042 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
fanningpj committed Dec 16, 2018
1 parent 4c0c8a9 commit e53d57c
Show file tree
Hide file tree
Showing 5 changed files with 308 additions and 158 deletions.
2 changes: 1 addition & 1 deletion src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ private static Function[] produceFunctions() {
// 316: TTEST
// 317: PROB
retval[318] = AggregateFunction.DEVSQ;
// 319: GEOMEAN
retval[319] = AggregateFunction.GEOMEAN;
// 320: HARMEAN
retval[321] = AggregateFunction.SUMSQ;
// 322: KURT
Expand Down
311 changes: 162 additions & 149 deletions src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Licensed to the Apache Software Foundation (ASF) under one or more

package org.apache.poi.ss.formula.functions;

import org.apache.commons.math3.stat.descriptive.moment.GeometricMean;
import org.apache.poi.ss.formula.eval.ErrorEval;
import org.apache.poi.ss.formula.eval.EvaluationException;
import org.apache.poi.ss.formula.eval.NumberEval;
Expand All @@ -28,45 +29,45 @@ Licensed to the Apache Software Foundation (ASF) under one or more
*/
public abstract class AggregateFunction extends MultiOperandNumericFunction {

private static final class LargeSmall extends Fixed2ArgFunction {
private final boolean _isLarge;
protected LargeSmall(boolean isLarge) {
_isLarge = isLarge;
}
private static final class LargeSmall extends Fixed2ArgFunction {
private final boolean _isLarge;
protected LargeSmall(boolean isLarge) {
_isLarge = isLarge;
}

public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
ValueEval arg1) {
double dn;
try {
ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
dn = OperandResolver.coerceValueToDouble(ve1);
} catch (EvaluationException e1) {
// all errors in the second arg translate to #VALUE!
return ErrorEval.VALUE_INVALID;
}
// weird Excel behaviour on second arg
if (dn < 1.0) {
// values between 0.0 and 1.0 result in #NUM!
return ErrorEval.NUM_ERROR;
}
// all other values are rounded up to the next integer
int k = (int) Math.ceil(dn);
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
ValueEval arg1) {
double dn;
try {
ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
dn = OperandResolver.coerceValueToDouble(ve1);
} catch (EvaluationException e1) {
// all errors in the second arg translate to #VALUE!
return ErrorEval.VALUE_INVALID;
}
// weird Excel behaviour on second arg
if (dn < 1.0) {
// values between 0.0 and 1.0 result in #NUM!
return ErrorEval.NUM_ERROR;
}
// all other values are rounded up to the next integer
int k = (int) Math.ceil(dn);

double result;
try {
double[] ds = ValueCollector.collectValues(arg0);
if (k > ds.length) {
return ErrorEval.NUM_ERROR;
}
result = _isLarge ? StatsLib.kthLargest(ds, k) : StatsLib.kthSmallest(ds, k);
NumericFunction.checkValue(result);
} catch (EvaluationException e) {
return e.getErrorEval();
}
double result;
try {
double[] ds = ValueCollector.collectValues(arg0);
if (k > ds.length) {
return ErrorEval.NUM_ERROR;
}
result = _isLarge ? StatsLib.kthLargest(ds, k) : StatsLib.kthSmallest(ds, k);
NumericFunction.checkValue(result);
} catch (EvaluationException e) {
return e.getErrorEval();
}

return new NumberEval(result);
}
}
return new NumberEval(result);
}
}

/**
* Returns the k-th percentile of values in a range. You can use this function to establish a threshold of
Expand All @@ -84,67 +85,67 @@ public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
* <li>If k is not a multiple of 1/(n - 1), PERCENTILE interpolates to determine the value at the k-th percentile.</li>
* </ul>
*/
private static final class Percentile extends Fixed2ArgFunction {

protected Percentile() {
}
private static final class Percentile extends Fixed2ArgFunction {

public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
ValueEval arg1) {
double dn;
try {
ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
dn = OperandResolver.coerceValueToDouble(ve1);
} catch (EvaluationException e1) {
// all errors in the second arg translate to #VALUE!
return ErrorEval.VALUE_INVALID;
}
if (dn < 0 || dn > 1) { // has to be percentage
return ErrorEval.NUM_ERROR;
}
protected Percentile() {
}

double result;
try {
double[] ds = ValueCollector.collectValues(arg0);
int N = ds.length;
public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0,
ValueEval arg1) {
double dn;
try {
ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex);
dn = OperandResolver.coerceValueToDouble(ve1);
} catch (EvaluationException e1) {
// all errors in the second arg translate to #VALUE!
return ErrorEval.VALUE_INVALID;
}
if (dn < 0 || dn > 1) { // has to be percentage
return ErrorEval.NUM_ERROR;
}

if (N == 0 || N > 8191) {
double result;
try {
double[] ds = ValueCollector.collectValues(arg0);
int N = ds.length;

if (N == 0 || N > 8191) {
return ErrorEval.NUM_ERROR;
}

double n = (N - 1) * dn + 1;
if (n == 1d) {
result = StatsLib.kthSmallest(ds, 1);
} else if (Double.compare(n, N) == 0) {
result = StatsLib.kthLargest(ds, 1);
} else {
int k = (int) n;
double d = n - k;
result = StatsLib.kthSmallest(ds, k) + d
* (StatsLib.kthSmallest(ds, k + 1) - StatsLib.kthSmallest(ds, k));
}
double n = (N - 1) * dn + 1;
if (n == 1d) {
result = StatsLib.kthSmallest(ds, 1);
} else if (Double.compare(n, N) == 0) {
result = StatsLib.kthLargest(ds, 1);
} else {
int k = (int) n;
double d = n - k;
result = StatsLib.kthSmallest(ds, k) + d
* (StatsLib.kthSmallest(ds, k + 1) - StatsLib.kthSmallest(ds, k));
}

NumericFunction.checkValue(result);
} catch (EvaluationException e) {
return e.getErrorEval();
}

NumericFunction.checkValue(result);
} catch (EvaluationException e) {
return e.getErrorEval();
}
return new NumberEval(result);
}
}

return new NumberEval(result);
}
}

static final class ValueCollector extends MultiOperandNumericFunction {
private static final ValueCollector instance = new ValueCollector();
public ValueCollector() {
super(false, false);
}
public static double[] collectValues(ValueEval...operands) throws EvaluationException {
return instance.getNumberArray(operands);
}
protected double evaluate(double[] values) {
throw new IllegalStateException("should not be called");
}
}
static final class ValueCollector extends MultiOperandNumericFunction {
private static final ValueCollector instance = new ValueCollector();
public ValueCollector() {
super(false, false);
}
public static double[] collectValues(ValueEval...operands) throws EvaluationException {
return instance.getNumberArray(operands);
}
protected double evaluate(double[] values) {
throw new IllegalStateException("should not be called");
}
}

protected AggregateFunction() {
super(false, false);
Expand Down Expand Up @@ -181,66 +182,66 @@ public boolean isSubtotalCounted(){
}

public static final Function AVEDEV = new AggregateFunction() {
protected double evaluate(double[] values) {
return StatsLib.avedev(values);
}
};
public static final Function AVERAGE = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
throw new EvaluationException(ErrorEval.DIV_ZERO);
}
return MathX.average(values);
}
};
public static final Function DEVSQ = new AggregateFunction() {
protected double evaluate(double[] values) {
return StatsLib.devsq(values);
}
};
public static final Function LARGE = new LargeSmall(true);
public static final Function MAX = new AggregateFunction() {
protected double evaluate(double[] values) {
return values.length > 0 ? MathX.max(values) : 0;
}
};
public static final Function MEDIAN = new AggregateFunction() {
protected double evaluate(double[] values) {
return StatsLib.median(values);
}
};
public static final Function MIN = new AggregateFunction() {
protected double evaluate(double[] values) {
return values.length > 0 ? MathX.min(values) : 0;
}
};
public static final Function PERCENTILE = new Percentile();
public static final Function PRODUCT = new AggregateFunction() {
protected double evaluate(double[] values) {
return MathX.product(values);
}
};
public static final Function SMALL = new LargeSmall(false);
public static final Function STDEV = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
throw new EvaluationException(ErrorEval.DIV_ZERO);
}
return StatsLib.stdev(values);
}
};
public static final Function SUM = new AggregateFunction() {
protected double evaluate(double[] values) {
return MathX.sum(values);
}
};
public static final Function SUMSQ = new AggregateFunction() {
protected double evaluate(double[] values) {
return MathX.sumsq(values);
}
};
protected double evaluate(double[] values) {
return StatsLib.avedev(values);
}
};
public static final Function AVERAGE = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
throw new EvaluationException(ErrorEval.DIV_ZERO);
}
return MathX.average(values);
}
};
public static final Function DEVSQ = new AggregateFunction() {
protected double evaluate(double[] values) {
return StatsLib.devsq(values);
}
};
public static final Function LARGE = new LargeSmall(true);
public static final Function MAX = new AggregateFunction() {
protected double evaluate(double[] values) {
return values.length > 0 ? MathX.max(values) : 0;
}
};
public static final Function MEDIAN = new AggregateFunction() {
protected double evaluate(double[] values) {
return StatsLib.median(values);
}
};
public static final Function MIN = new AggregateFunction() {
protected double evaluate(double[] values) {
return values.length > 0 ? MathX.min(values) : 0;
}
};

public static final Function PERCENTILE = new Percentile();

public static final Function PRODUCT = new AggregateFunction() {
protected double evaluate(double[] values) {
return MathX.product(values);
}
};
public static final Function SMALL = new LargeSmall(false);
public static final Function STDEV = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
throw new EvaluationException(ErrorEval.DIV_ZERO);
}
return StatsLib.stdev(values);
}
};
public static final Function SUM = new AggregateFunction() {
protected double evaluate(double[] values) {
return MathX.sum(values);
}
};
public static final Function SUMSQ = new AggregateFunction() {
protected double evaluate(double[] values) {
return MathX.sumsq(values);
}
};
public static final Function VAR = new AggregateFunction() {
protected double evaluate(double[] values) throws EvaluationException {
if (values.length < 1) {
Expand All @@ -257,4 +258,16 @@ protected double evaluate(double[] values) throws EvaluationException {
return StatsLib.varp(values);
}
};
public static final Function GEOMEAN = new AggregateFunction() {
@Override
protected double evaluate(double[] values) throws EvaluationException {
// The library implementation returns 0 for an input sequence like [1, 0]. So this check is necessary.
for (double value: values) {
if (value <= 0) {
throw new EvaluationException(ErrorEval.NUM_ERROR);
}
}
return new GeometricMean().evaluate(values, 0, values.length);
}
};
}
Loading

0 comments on commit e53d57c

Please sign in to comment.