1
\$\begingroup\$

I've been tooling around with a Statistical suite in Java, for use in a few machine learning projects (I know, 'ML in Java?'). I am looking for any improvements to be made here, with a specific focus on the precision of results:

package com.glass.wood.statistics;

import java.util.List;

/**
 * This class acts as a package for a number of statistical operations including:
 *      Mean
 *      Min/Max 
 *      Sum of Squared Error
 *      Mean Squared Error(Variance)
 *      Standard Deviation (Standard Error)
 *      Sum
 *      Square Sum
 *      R correlation
 *      Covariance
 *      Linear fit equation
 *      input*output Product Sum  
 * @author wood
 */
public class Stat {     
    //--------------------------------------------------------------------------------------------------------------
    //      Basic Analysis
    //--------------------------------------------------------------------------------------------------------------        
    /**
     * Computes the Sample Mean by creating a running summation of the values and then dividing by the
     * number of values in the set
     * @return double
     */
    public static double mean(List<Number> data) {
        double sum = 0;
        for(Number e: data){
            sum += e.doubleValue();
        }
        return sum / data.size();
    }


    public static Number median(List<Number> data){
        if(data.size()%2 != 0){
            return data.get(data.size()/2);
        }
        Number temp1 = data.get(data.size()/2);
        Number temp2 = data.get((data.size()/2)-1);
        return (temp1.doubleValue() + temp2.doubleValue())/2;
    }

    public static double max(List<Number> data){
        double temp = -Double.MAX_VALUE;
        for(int i = 0; i < data.size(); i++){
            if(data.get(i).doubleValue() > temp){
                temp = data.get(i).doubleValue();
            }
        }
        return temp;
    }

    public static double min(List<Number> data){
        double temp = Double.MAX_VALUE;
        for(int i = 0; i < data.size(); i++){
            if(data.get(i).doubleValue() < temp){
                temp = data.get(i).doubleValue();
            }
        }
        return temp;
    }

    /**
     * Computes the Sum of the Squared Error for the sample, which is used to determine the variance and 
     * standard error
     * @return double
     */
    public static double squaredError(List<Number> data){
        double temp;
        double sum = 0;
        double mean = mean(data);
        for (Number e: data) {
            temp = Math.pow(e.doubleValue() - mean, 2);
            sum += temp;
        }
        return sum;
    }

    /**
     * The sample variance carries a bias of n-1/n, where n is the size of the sample. Multiplying this values 
     * by n/n-1 removes this bias as an estimate of the population variance. This results in the variance 
     * being calculated with n-1 as opposed to n
     * @return double
     */
    public static double variance(List<Number> data) {
        return squaredError(data)/(data.size()-1);
    }

    /**
     * The covariance carries the same bias as variance, thus we divide by n-1
     * @return double
     */
    public static double covariance(List<Number> xData, List<Number> yData){
        double runSum = 0;
        double xMean = mean(xData);
        double yMean = mean(yData);
        for(int i = 0; i < xData.size(); i++){
            runSum += (xData.get(i).doubleValue() - xMean) * (yData.get(i).doubleValue() - yMean);
        }
        return runSum/(xData.size() -1);
    }

    /**
     * As a population estimate, the samples standard error carries a bias of (sqrt(n-1.5)/sqrt(n)). Removing
     * this bias, as above with variance, results in calculating with sqrt(n-1.5) as the denominator
     * @return
     */
    public static double standardError(List<Number> data){
        return Math.sqrt(squaredError(data) / (data.size() - 1.5));
    }
    //--------------------------------------------------------------------------------------------------------------
    //      Summations
    //--------------------------------------------------------------------------------------------------------------

    //The methods below return summations of the given data

    public static double sum(List<Number> data){
        double tempSum = 0;
        for(Number item : data){
            tempSum += item.doubleValue();
        }
        return tempSum;
    }

    public static double productSum(List<Number> data1, List<Number> data2){
        double tempSum = 0;
        for(int i = 0; i < data1.size(); i++){
            tempSum += (data1.get(i).doubleValue() * data2.get(i).doubleValue());
        }
        return tempSum;
    }

    public static double squareSum(List<Number> data){
        double tempSum = 0;
        for(Number item: data){
            tempSum += Math.pow(item.doubleValue(), 2);
        }
        return tempSum;
    }
    //--------------------------------------------------------------------------------------------------------------
    //      Regression Analysis
    //--------------------------------------------------------------------------------------------------------------        

    //The methods below perform regression on the samples input and output to  a linear equation
    //of form Slope*(input) + Intercept = (output). R correlation is returned as a decimal between 0 and 1

    public static double correlation(List<Number> xData, List<Number> yData){
        double xSum = sum(xData);
        double ySum = sum(yData);
        double numerator = (xData.size() * productSum(xData, yData)) - (xSum * ySum);
        double denominatorLeft = (xData.size() * squareSum(xData)) - (Math.pow(xSum, 2));
        double denominatorRight = (yData.size() * squareSum(yData)) - (Math.pow(ySum, 2));

        return numerator/(Math.sqrt(denominatorLeft*denominatorRight)); 
    }

    public static double rSquare(List<Number> xData, List<Number> yData){
        return Math.pow(correlation(xData,yData), 2);
    }

    public static LinearEquation linearFit(List<Number> xData, List<Number> yData){
        double xSum = sum(xData);
        double ySum = sum(yData);
        double xySum = productSum(xData, yData);
        double x2Sum = squareSum(xData);
        double slope = slope(xySum, xSum, ySum, x2Sum, xData.size());
        double intercept = intercept(xySum, xSum, ySum, x2Sum, xData.size());


        LinearEquation toReturn = new LinearEquation(slope, intercept);
        return toReturn;
    }

    private static double slope(double xySum, double xSum, double ySum, double x2Sum, int size) {
        double numerator = (size*xySum) - (xSum*ySum);
        double denominator = (size*x2Sum) - Math.pow(xSum, 2);
        return numerator/denominator;
    }

    private static double intercept(double xySum, double xSum, double ySum, double x2Sum, int size) {
        double numerator = (ySum*x2Sum) - (xSum*xySum);
        double denominator = (size*x2Sum) - Math.pow(xSum, 2);
        return numerator/denominator;
    }
} 
\$\endgroup\$
3
  • \$\begingroup\$ Considering you're mentioning Machine Learning, are you not also interested in the performance? Are you using any ML library in Java? \$\endgroup\$ Jan 3, 2018 at 16:16
  • \$\begingroup\$ Performance is also of interest, yes, but I can handle a long running program so long as its predictions are precise, thus the focus. I am not currently using an ML library though I would happily take suggestions. I am mostly trying to wrap my head around different ML approaches by implementing them (like the Newton-Raphson method, for example). This is mostly intended as a way to interact with the data without modification. I was also considering making a 'Sample' object to compute this information at creation, making them available to the my ML flavor of the month. \$\endgroup\$
    – DapperDan
    Jan 3, 2018 at 16:37
  • 1
    \$\begingroup\$ Just want to point out that the Apache Commons has a very large Java math library, including all the statistical functions you implemented here. commons.apache.org/proper/commons-math \$\endgroup\$
    – markspace
    Jan 4, 2018 at 2:44

1 Answer 1

2
\$\begingroup\$

A concern is the use of List<Number>. It looks like being general, but it applies to no data you'll get out of any sane library. When you get double[] or float[], then you'll see that List<Number> is not really that general. There's no good solution.

Actually, even when you get List<Double>, it won't work. You'd have to declare your methods using List<? extends Number>.

I've been tooling around with a Statistical suite in Java, for use in a few machine learning projects (I know, 'ML in Java?').

ML in Java? Why not, but by using List<Number> you're using a factor of two or three on memory and maybe an order of magnitude on speed.

mean

There's a better algorithm producing smaller rounding error. IIRC correctly, it's also slower, so your solution is OK.

median

You're boldly assuming that your sequence is sorted.

max

Why don't you just return the last element? Or did you give up the assumption in the meantime?

double temp = -Double.MAX_VALUE;

I'd only use the name "temp" when no better name is available. Here it's the "result" or maybe "candidate".

for(int i = 0; i < data.size(); i++){

You need no i, use a foreach loop.

if(data.get(i).doubleValue() > temp){
     temp = data.get(i).doubleValue();
}

This sounds a bit inefficient. The JIT may optimize the overhead, but it won't make it any more readable. Use a temporary,

public static double squaredError(List<Number> data){
    double temp;

This is a needless variable defined in a needlessly wide scope.

temp = Math.pow(e.doubleValue() - mean, 2);

IIRC Math.pow(x, 2) gets special handling in Hotspot, however, note that without the optimization, it'd be maybe ten times slower than x*x. And also a bit more imprecise.

public static double variance(List<Number> data) {
    return squaredError(data)/(data.size()-1);

What about single element lists? Whatever behavior you decide for, it should be documented.

I'm stopping here as it's late. Overall, it's not bad, but you may want to reconsider the data type used. As there's no good solution, I'd wait for some real inputs.

Note that, List<Byte> is much more memory consuming that double[] of the same length.

If I should choose a data format just now, I'd probably go for a simple class aggregating double[] values, int offset, int length.

\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.