Reputation: 79
I would like to create a simple class applying common statistics using lambda expression. I am wondering how can I avoid using the switch case in the statistic() method?
For example, I may want to write a new lambda to calculate the variance of the list, etc.
Thank you.
public class DescriptiveStatistics {
public static void main(String[] args) {
List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0);
numbers.stream().forEach(n-> System.out.print(n + " "));
System.out.println();
System.out.println("Descriptive statistics");
System.out.println("Sum: " + statistic(numbers, "Sum"));
System.out.println("Max: " + statistic(numbers, "Max"));
System.out.println("Min: " + statistic(numbers, "Min"));
System.out.println("Average: " + statistic(numbers, "Average"));
System.out.println("Count: " + statistic(numbers, "Count"));
}
private static double statistic(List<Double> numbers, String function) {
switch (function.toLowerCase()) {
case "sum":
return numbers.stream().mapToDouble(Double::doubleValue).sum();
case "max":
return numbers.stream().mapToDouble(Double::doubleValue).max().getAsDouble();
case "min":
return numbers.stream().mapToDouble(Double::doubleValue).min().getAsDouble();
case "average":
return numbers.stream().mapToDouble(Double::doubleValue).average().getAsDouble();
case "count":
return numbers.stream().mapToDouble(Double::doubleValue).count();
}
return 0;
}
I have in mind of a method like this
private static double newStatistics(List<Double> numbers, Function<Double, Double> function){
return numbers.stream().mapToDouble(Double::doubleValue).function();
}
Upvotes: 5
Views: 3800
Reputation: 328598
Why not simply use DoubleStream#summaryStatistics or apply a similar pattern?
You could even extend the class to add custom methods, say a variance, skewness and kurtosis for example:
/**
* Algorithms derived from: Philippe Pébay, Formulas for Robust, One-Pass Parallel
* Computation of Covariances and Arbitrary-Order Statistical Moments.
*/
public class MoreDoubleStatistics extends DoubleSummaryStatistics {
private double M1, M2, M3, M4;
@Override
public void accept(double x) {
super.accept(x);
long n = getCount();
double delta = x - M1; // δ
double delta_n = delta / n; // δ / n
double delta2_n = delta * delta_n; // δ^2 / n
double delta2_n2 = delta_n * delta_n; // δ^2 / n^2
double delta3_n2 = delta2_n * delta_n; // δ^3 / n^2
double delta4_n3 = delta3_n2 * delta_n; // δ^4 / n^3
M4 += (n - 1) * (n * n - 3 * n + 3) * delta4_n3
+ 6 * M2 * delta2_n2
- 4 * M3 * delta_n;
M3 += (n - 1) * (n - 2) * delta3_n2
- 3 * M2 * delta_n;
M2 += (n - 1) * delta2_n;
M1 += delta_n;
}
@Override
public void combine(DoubleSummaryStatistics other) {
throw new UnsupportedOperationException(
"Can't combine a standard DoubleSummaryStatistics with this class");
}
public void combine(MoreDoubleStatistics other) {
MoreDoubleStatistics s1 = this;
MoreDoubleStatistics s2 = other;
long n1 = s1.n();
long n2 = s2.n();
long n = n1 + n2;
double delta = s2.M1 - s1.M1; // δ
double delta_n = delta / n; // δ / n
double delta2_n = delta * delta_n; // δ^2 / n
double delta2_n2 = delta_n * delta_n; // δ^2 / n^2
double delta3_n2 = delta2_n * delta_n; // δ^3 / n^2
double delta4_n3 = delta3_n2 * delta_n; // δ^4 / n^3
this.M4 = s1.M4 + s2.M4 + n1 * n2 * (n1 * n1 - n1 * n2 + n2 * n2) * delta4_n3
+ 6.0 * (n1 * n1 * s2.M2 + n2 * n2 * s1.M2) * delta2_n2
+ 4.0 * (n1 * s2.M3 - n2 * s1.M3) * delta_n;
this.M3 = s1.M3 + s2.M3 + n1 * n2 * (n1 - n2) * delta3_n2
+ 3.0 * (n1 * s2.M2 - n2 * s1.M2) * delta_n;
this.M2 = s1.M2 + s2.M2 + n1 * n2 * delta2_n;
this.M1 = s1.M1 + n2 * delta;
super.combine(other);
}
private long n() { return getCount(); }
public double mean() { return getAverage(); }
public double variance() { return n() <= 1 ? 0 : M2 / (n() - 1); }
public double stdDev() { return sqrt(variance()); }
public double skew() { return M2 == 0 ? 0 : sqrt(n()) * M3/ pow(M2, 1.5); }
public double kurtosis() { return M2 == 0 ? 0 : n() * M4 / (M2 * M2) - 3.0; }
}
Upvotes: 11
Reputation: 50044
Replace the String parameter of the method statistic with a function type, that takes a DoubleStream and returns the aggregate.
private static double statistic(List<Double> numbers,
ToDoubleFunction<DoubleStream> function) {
return function.applyAsDouble(
numbers.stream().mapToDouble(Double::doubleValue));
}
Now, you can invoke the method as follows, without using a switch statement for the different operations on the stream:
System.out.println("Sum: " + statistic(numbers, s -> s.sum()));
System.out.println("Max: " + statistic(numbers, s -> s.max().getAsDouble()));
System.out.println("Min: " + statistic(numbers, s -> s.min().getAsDouble()));
System.out.println("Average: " + statistic(numbers, s -> s.average().getAsDouble()));
System.out.println("Count: " + statistic(numbers, s -> s.count()));
Upvotes: 8