/*
 * This file is part of the UEA Time Series Machine Learning (TSML) toolbox.
 *
 * The UEA TSML toolbox 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, either version 3 of the License, or 
 * (at your option) any later version.
 *
 * The UEA TSML toolbox is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with the UEA TSML toolbox. If not, see <https://www.gnu.org/licenses/>.
 */
 

/*
 * Created on Jan 29, 2006
 *
 */
package statistics.transformations;

import fileIO.*;
import weka.core.*;
import weka.classifiers.functions.*;
import weka.classifiers.*;
/**
 * @author ajb
 *
 */
public class BoxCox extends Transformations{

    public static double MIN=-3,MAX=3,INTERVAL=0.25;
    boolean tryZero=false;
    AbstractClassifier c;
    double minError=Double.MAX_VALUE, bestLambda;
    double gamma;
    boolean strictlyPositive=true;

    public BoxCox()
    {
        supervised=true;
        response=true;
        minError=Double.MAX_VALUE;
        bestLambda=MIN;
        c=new LinearRegression();
        String[] options = {"-S 1","-C "};
        try{
                c.setOptions(options);
        }catch(Exception e){
                System.out.println(" Error Setting options in constructor");
        }
    }
    public void setStrictlyPos(boolean f){strictlyPositive=f;}
    public BoxCox(AbstractClassifier c)
    {
        this();
        this.c=c;
    }
//Performs a specific B-C transform on the response variable, overwriting original
    static public void transformResponse(Instances data, double lambda, double[] response)
    {
        Instance inst;
        double v;
        int responsePos=data.numAttributes()-1;
        for(int i=0;i<response.length;i++)
        {
            inst=data.instance(i);
            v=(Math.pow(response[i],lambda)-1)/lambda;
            inst.setValue(responsePos,v);
        }
    }

//	Transform the response variable using box-cox procedure
    public Instances transform(Instances data)
    {
        int responsePos=data.classIndex();
        double[] response=data.attributeToDoubleArray(responsePos);
        double[] predictions=new double[response.length];	
        double v;
        Instance inst;
//Check if strictly positive
        gamma=response[0];
        for(int i=1;i<response.length;i++)
        {
            if(response[i]<gamma)
                gamma=response[i];
        }
        System.out.println(" Min value = "+gamma);
        if(gamma<=0)
        {
            gamma=-2*gamma+1;
            System.out.println(" Data series is not strictly positive, rescaling by "+gamma);
            for(int i=0;i<response.length;i++)
                response[i]+=gamma;
        }	

        for(double lambda=MIN;lambda<=MAX;lambda+=INTERVAL)
        {
//Transform response			
            if(lambda==0) lambda+=INTERVAL;
            transformResponse(data,lambda,response);

//Fit model and get training predictions
            try{
                c.buildClassifier(data);
                for(int i=0;i<predictions.length;i++)
                {
                    inst=data.instance(i);
                    predictions[i]=c.classifyInstance(inst);
//					if(predictions[i]<0)
//						predictions[i]=0;
                }
            }
            catch(Exception e)
            {
                System.out.println(" Error building with lambda = "+lambda);
            }
//Assess quality of fit by SSE: Transformed or untransformed? Assume we have to 
// turn it back

            double SSE=0;
            boolean f=true;
            for(int i=0;i<predictions.length;i++)
            {
                predictions[i]*=lambda;
                predictions[i]++;
                if(predictions[i]<=0)
                        predictions[i]=0;
                else
                {
                        if(lambda>0)
                                predictions[i]=Math.pow(predictions[i],1.0/lambda);
                        else
                                predictions[i]=1/Math.pow(predictions[i],-1.0/lambda);
                }	
                SSE+=(predictions[i]-response[i])*(predictions[i]-response[i]);
            }
//Check whether minimum, and store
            SSE/=(data.numInstances()-data.numAttributes());
            System.out.println("lambda = "+lambda+"SSE ="+SSE);
            if(SSE<minError)
            {
                minError=SSE;
                bestLambda=lambda;
            }
        }
        System.out.println("Min lambda = "+bestLambda+" with MSE = "+minError);
//Perform best transform
        for(int i=0;i<response.length;i++)
        {
            inst=data.instance(i);
            v=(Math.pow(response[i],bestLambda)-1)/bestLambda;
            inst.setValue(responsePos,v);
        }
        return data;

    }
    public Instances invert(Instances data){
        Instance inst;
        int responsePos=data.numAttributes()-1;
        double[] response=data.attributeToDoubleArray(responsePos);
        double v;

        for(int i=0;i<data.numInstances();i++)
        {
            inst=data.instance(i);
            v=response[i]*bestLambda;
            v++;
            v=Math.pow(v,1/bestLambda);			
            inst.setValue(responsePos,v);
        }
        return data;
    }
//Transform data based on values formed by calling transform on another data set
//Only needed for dependent variable transformations, for others does nothing
    public Instances staticTransform(Instances data)
    {
        Instance inst;
        int responsePos=data.numAttributes()-1;
        double[] response=data.attributeToDoubleArray(responsePos);
        double v;

        for(int i=0;i<data.numInstances();i++)
        {
            inst=data.instance(i);
            v=(Math.pow(response[i],bestLambda)-1)/bestLambda;
            inst.setValue(responsePos,v);
        }
        return data;
    }
    public double[] invertPredictedResponse(double[] d)
    {
        double v;
        for(int i=0;i<d.length;i++)
        {
                v=d[i]*bestLambda;
                v++;
                d[i]=Math.pow(v,1/bestLambda);			
        }
        return d;
    }
    public static void main(String[] args)
    {
        double[] quantiles = Transformations.getNormalQuantiles(0.0,1.0);
        for(int i=0;i<quantiles.length;i++)
                System.out.println("Quantile "+i+" = "+quantiles[i]);
        OutFile of = new OutFile("TestQuantiles.csv");
        for(int i=0;i<quantiles.length;i++)
        {
                System.out.println(i+","+(i+1)/(double)quantiles.length+","+quantiles[i]);
                of.writeLine(i+","+(i+1)/(double)quantiles.length+","+quantiles[i]);
        }
    }

}
