/*
* Copyright (c) 2010 The Jackson Laboratory
*
* This 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.
*
* This software 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 this software. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jax.maanova.test;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.jax.maanova.fit.MixedModelSolutionMethod;
import org.jax.r.RAssignmentCommand;
import org.jax.r.RCommand;
import org.jax.r.RCommandBuilder;
import org.jax.r.RCommandParameter;
import org.jax.r.RMethodInvocationCommand;
import org.jax.r.RUtilities;
import org.jax.util.math.NumericUtilities;
/**
* For building matest commands
* @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A>
*/
public class TestModelCommandBuilder implements RCommandBuilder
{
/**
* Shuffle method to apply to permutations
*/
public enum ShuffleMethod {
/**
* see R/maanova docs
*/
SAMPLE
{
/**
* {@inheritDoc}
*/
@Override
public String getRParameterString()
{
return "sample";
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "Sample";
}
},
/**
* see R/maanova docs
*/
RESIDUAL
{
/**
* {@inheritDoc}
*/
@Override
public String getRParameterString()
{
return "resid";
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "Residual";
}
};
/**
* Getter for the string that R/maanova uses to represent this shuffle
* method
* @return the R/maanova string (no quotes, you have to add those)
*/
public abstract String getRParameterString();
}
/**
* The F-statistic(s) that should be caluculated
*/
public enum FStatisticToCalculate {
/**
* see R/maanova docs
*/
JustStandardFStatistic {
/**
* {@inheritDoc}
*/
@Override
public String getRParameter()
{
return "c(1, 0)";
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "Standard F Statistic";
}
},
/**
* see R/maanova docs
*/
JustFSStatistic {
/**
* {@inheritDoc}
*/
@Override
public String getRParameter()
{
return "c(0, 1)";
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "FS Statistic";
}
},
/**
* see R/maanova docs
*/
BothFAndFsStatistics {
/**
* {@inheritDoc}
*/
@Override
public String getRParameter()
{
return "c(1, 1)";
}
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return "Both F and Fs Statistics";
}
};
/**
* Getter for the r parameter to use.
* @return
* the R parameter
*/
public abstract String getRParameter();
}
/**
* The R method name to use
*/
private static final String METHOD_NAME = "matest";
private volatile String testResultDataName;
private volatile String madataParameter;
private volatile String fitResultParameter;
private volatile Number[][] fTestContrastMatrix;
private volatile Number[][] tTestContrastMatrix;
private volatile String[] termsToTest;
private volatile String[] levelsToTest;
private volatile int permutationCount = 1000;
private volatile double criticalThreshold = 0.9;
private volatile TestType testType;
private volatile ShuffleMethod shuffleMethod;
private volatile MixedModelSolutionMethod mixedModelSolutionMethod;
private volatile FStatisticToCalculate fStatisticToCalculate;
private volatile boolean poolPValues = true;
private volatile boolean verbose = true;
/**
* getter for the test result data name
* @return the testResultDataName
*/
public String getTestResultDataName()
{
return this.testResultDataName;
}
/**
* setter for the test result data name
* @param testResultDataName the testResultDataName to set
*/
public void setTestResultDataName(String testResultDataName)
{
this.testResultDataName = testResultDataName;
}
/**
* Getter for the madata parameter
* @return the parameter
*/
public String getMadataParameter()
{
return this.madataParameter;
}
/**
* Setter for the madata parameter
* @param madataParameter the parameter to set
*/
public void setMadataParameter(String madataParameter)
{
this.madataParameter = madataParameter;
}
/**
* Getter for the fit result parameter
* @return the fit result parameter
*/
public String getFitResultParameter()
{
return this.fitResultParameter;
}
/**
* Setter for the fit result parameter
* @param fitResultParameter the new parameter value
*/
public void setFitResultParameter(String fitResultParameter)
{
this.fitResultParameter = fitResultParameter;
}
/**
* Getter for the contrast matrix that should be used when
* {@link #getTestType()} == {@link TestType#F_TEST}. Null means
* don't pass any matrix to the R command
* @return the matrix
*/
public Number[][] getFTestContrastMatrix()
{
return this.fTestContrastMatrix;
}
/**
* Setter for the F-test contrast matrix
* @see #getFTestContrastMatrix()
* @param fTestContrastMatrix the matrix
*/
public void setFTestContrastMatrix(Number[][] fTestContrastMatrix)
{
this.fTestContrastMatrix = fTestContrastMatrix;
}
/**
* Getter for the contrast matrix that should be used when
* {@link #getTestType()} == {@link TestType#T_TEST}. Null means
* use the all pairs matrix.
* @return the tTestContrastMatrix
*/
public Number[][] getTTestContrastMatrix()
{
return this.tTestContrastMatrix;
}
/**
* Setter for the t-test contrast matrix
* @see #getTTestContrastMatrix()
* @param testContrastMatrix the contrast matrix to use for a t-test
*/
public void setTTestContrastMatrix(Number[][] testContrastMatrix)
{
this.tTestContrastMatrix = testContrastMatrix;
}
/**
* Setter for the terms that should be tested
* @param termsToTest the termsToTest to set
*/
public void setTermsToTest(String[] termsToTest)
{
this.termsToTest = termsToTest;
}
/**
* Getter for the terms that should be tested
* @return the termsToTest
*/
public String[] getTermsToTest()
{
return this.termsToTest;
}
/**
* Getter for all of the term levels that should be tested
* @see #getTermsToTest()
* @return the levels
*/
public String[] getLevelsToTest()
{
return this.levelsToTest;
}
/**
* Setter for the levels to test. This should be all of the terms available
* from {@link #getTermsToTest()}
* @param levelsToTest the levels
*/
public void setLevelsToTest(String[] levelsToTest)
{
this.levelsToTest = levelsToTest;
}
/**
* Getter for the permutation cont
* @return the permutation count
*/
public int getPermutationCount()
{
return this.permutationCount;
}
/**
* Setter for the permutation count
* @param permutationCount the permutation count
*/
public void setPermutationCount(int permutationCount)
{
this.permutationCount = permutationCount;
}
/**
* Getter for the critical threshold
* @return the critical threshold
*/
public double getCriticalThreshold()
{
return this.criticalThreshold;
}
/**
* Setter for the critical threshold
* @param criticalThreshold
* the criticalThreshold to set
*/
public void setCriticalThreshold(double criticalThreshold)
{
this.criticalThreshold = criticalThreshold;
}
/**
* Getter for the test type
* @return the test type
*/
public TestType getTestType()
{
return this.testType;
}
/**
* Setter for the test type
* @param testType
* the testType to set
*/
public void setTestType(TestType testType)
{
this.testType = testType;
}
/**
* Getter for the shuffle method
* @return the shuffle method
*/
public ShuffleMethod getShuffleMethod()
{
return this.shuffleMethod;
}
/**
* Setter for the shuffle method
* @param shuffleMethod the shuffle method
*/
public void setShuffleMethod(ShuffleMethod shuffleMethod)
{
this.shuffleMethod = shuffleMethod;
}
/**
* Getter for the MME method we should use
* @return the method to use
*/
public MixedModelSolutionMethod getMixedModelSolutionMethod()
{
return this.mixedModelSolutionMethod;
}
/**
* Setter for the MME method we should use
* @param mixedModelSolutionMethod the method to use
*/
public void setMixedModelSolutionMethod(
MixedModelSolutionMethod mixedModelSolutionMethod)
{
this.mixedModelSolutionMethod = mixedModelSolutionMethod;
}
/**
* Getter for the F statistic that should be calculated
* @return the statistic
*/
public FStatisticToCalculate getFStatisticToCalculate()
{
return this.fStatisticToCalculate;
}
/**
* Setter for the F statistic that should be calculated
* @param statisticToCalculate the statistic
*/
public void setFStatisticToCalculate(
FStatisticToCalculate statisticToCalculate)
{
this.fStatisticToCalculate = statisticToCalculate;
}
/**
* Getter to determine if we're pooling p-values
* @return true iff we should pool p-values
*/
public boolean getPoolPValues()
{
return this.poolPValues;
}
/**
* Setter for deciding if we will pool p-values
* @param poolPValues if true then we pool p-values
*/
public void setPoolPValues(boolean poolPValues)
{
this.poolPValues = poolPValues;
}
/**
* determines if we're using verbose output
* @return the verbose
*/
public boolean isVerbose()
{
return this.verbose;
}
/**
* Modifies the verbosity setting of the command
* @param verbose the verbose to set
*/
public void setVerbose(boolean verbose)
{
this.verbose = verbose;
}
/**
* {@inheritDoc}
*/
public RCommand getCommand()
{
List<RCommandParameter> commandParameters = this.getCommandParameters();
RMethodInvocationCommand readMadataMethodCommand = new RMethodInvocationCommand(
METHOD_NAME,
commandParameters);
String dataName = this.getTestResultDataName();
if(dataName == null || dataName.trim().length() == 0)
{
return readMadataMethodCommand;
}
else
{
return new RAssignmentCommand(
dataName.trim(),
readMadataMethodCommand.getCommandText());
}
}
/**
* Get the command parameters for the matest command
* @return
* the parameters
*/
private List<RCommandParameter> getCommandParameters()
{
List<RCommandParameter> commandParameters =
new ArrayList<RCommandParameter>();
String madataParameter = this.madataParameter;
if(madataParameter != null)
{
// maybe add the data parameter
commandParameters.add(new RCommandParameter(
"data",
madataParameter));
}
// maybe add the fit result parameter
String fitResultParameter = this.fitResultParameter;
if(fitResultParameter != null)
{
commandParameters.add(new RCommandParameter(
"anovaobj",
fitResultParameter));
}
// maybe add the term parameter
String[] termsToTest = this.termsToTest;
if(termsToTest != null && termsToTest.length >= 0)
{
if(termsToTest.length == 1)
{
// for one we should just use the string by itself
commandParameters.add(new RCommandParameter(
"term",
RUtilities.javaStringToRString(termsToTest[0])));
}
else
{
// for more than one we need a vector
String termsRVector = RUtilities.stringArrayToRVector(termsToTest);
commandParameters.add(new RCommandParameter(
"term",
termsRVector));
}
}
TestType testType = this.testType;
if(testType != null)
{
commandParameters.add(new RCommandParameter(
"test.type",
RUtilities.javaStringToRString(testType.getRParameterString())));
}
boolean tTest = testType == TestType.T_TEST;
Number[][] contrastMatrix =
tTest ? this.tTestContrastMatrix : this.fTestContrastMatrix;
if(contrastMatrix == null)
{
if(tTest)
{
String[] levelsToTest = this.levelsToTest;
if(levelsToTest != null)
{
RCommandParameter levelCountParameter =
new RCommandParameter(Integer.toString(levelsToTest.length));
RCommand allPairwiseRCommand = new RMethodInvocationCommand(
"PairContrast",
new RCommandParameter[] {levelCountParameter});
commandParameters.add(new RCommandParameter(
"Contrast",
allPairwiseRCommand.getCommandText()));
}
}
else
{
// for F-test do nothing here
}
}
else
{
String newContrastMatrixRCommand =
this.toNewRMatrixCommand(contrastMatrix).getCommandText();
commandParameters.add(new RCommandParameter(
"Contrast",
newContrastMatrixRCommand));
}
MixedModelSolutionMethod mixedModelSolutionMethod =
this.mixedModelSolutionMethod;
if(mixedModelSolutionMethod != null)
{
commandParameters.add(new RCommandParameter(
"MME.method",
RUtilities.javaStringToRString(mixedModelSolutionMethod.getRParameterString())));
}
FStatisticToCalculate fStatisticToCalculate = this.fStatisticToCalculate;
if(fStatisticToCalculate != null)
{
commandParameters.add(new RCommandParameter(
"test.method",
fStatisticToCalculate.getRParameter()));
}
int permutationCount = this.permutationCount;
commandParameters.add(new RCommandParameter(
"n.perm",
Integer.toString(permutationCount)));
if(permutationCount >= 2)
{
BigDecimal roundedCritical = NumericUtilities.roundToDecimalPositionBigDecimal(
this.criticalThreshold,
-3);
commandParameters.add(new RCommandParameter(
"critical",
roundedCritical.toString()));
ShuffleMethod shuffleMethod = this.shuffleMethod;
if(shuffleMethod != null)
{
commandParameters.add(new RCommandParameter(
"shuffle.method",
RUtilities.javaStringToRString(
shuffleMethod.getRParameterString())));
}
commandParameters.add(new RCommandParameter(
"pval.pool",
RUtilities.javaBooleanToRBoolean(this.poolPValues)));
}
commandParameters.add(new RCommandParameter(
"verbose",
RUtilities.javaBooleanToRBoolean(this.verbose)));
return commandParameters;
}
/**
* Convert the given 2D number array into an R new matrix command
* @param contrastMatrix
* the 2D array to convert
* @return
* the R {@code new("matrix", ...)} command
*/
private RCommand toNewRMatrixCommand(Number[][] contrastMatrix)
{
final int nrow = contrastMatrix.length;
final int ncol;
if(nrow == 0)
{
ncol = 0;
}
else
{
ncol = contrastMatrix[0].length;
}
List<RCommandParameter> rNewParams = new ArrayList<RCommandParameter>();
rNewParams.add(new RCommandParameter(
RUtilities.javaStringToRString("matrix")));
rNewParams.add(new RCommandParameter(
"nrow",
Integer.toString(nrow)));
rNewParams.add(new RCommandParameter(
"ncol",
Integer.toString(ncol)));
rNewParams.add(new RCommandParameter(
"byrow",
RUtilities.javaBooleanToRBoolean(true)));
if(nrow != 0 && ncol != 0)
{
final Number[] contrastArray = new Number[ncol * nrow];
for(int row = 0; row < nrow; row++)
{
// make sure all of the row lengths match
assert row == 0 || contrastMatrix[row - 1].length == contrastMatrix[row].length;
final int rowStart = row * ncol;
for(int col = 0; col < ncol; col++)
{
contrastArray[rowStart + col] = contrastMatrix[row][col];
}
}
rNewParams.add(new RCommandParameter(
RUtilities.objectArrayToRVector(contrastArray)));
}
return new RMethodInvocationCommand("new", rNewParams);
}
}