/* * Copyright (c) 2009 The Jackson Laboratory * * This software was developed by Gary Churchill's Lab at The Jackson * Laboratory (see http://research.jax.org/faculty/churchill). * * 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.qtl.cross; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.jax.analyticgraph.data.NamedCategoricalData; import org.jax.analyticgraph.data.NamedData; import org.jax.analyticgraph.data.NamedDataMatrix; import org.jax.analyticgraph.data.NamedIntegerData; import org.jax.analyticgraph.data.NamedRealData; import org.jax.analyticgraph.data.SimpleSelectableNamedDataMatrix; import org.jax.qtl.fit.FitQtlResult; import org.jax.qtl.scan.ScanOneResult; import org.jax.qtl.scan.ScanTwoResult; import org.jax.r.RCommand; import org.jax.r.jriutilities.JRIUtilityFunctions; import org.jax.r.jriutilities.RInterface; import org.jax.r.jriutilities.RObject; import org.jax.r.jriutilities.SilentRCommand; import org.jax.util.ObjectUtil; import org.rosuda.JRI.REXP; import org.rosuda.JRI.RFactor; /** * Represents an R cross object * @author <A HREF="mailto:keith.sheppard@jax.org">Keith Sheppard</A> */ public class Cross extends RObject { /** * Our logger */ private static final Logger LOG = Logger.getLogger(Cross.class.getName()); /** * the R type that crosses should carry */ public static final String TYPE_STRING = "cross"; /** * Possible cross sub-types in R/qtl */ public static enum CrossSubType { /** * an F2 cross */ F2 { private final String[] markerCategoricalValues = new String[] { "AA", // 1 "AB", // 2 "BB", // 3 "Not BB", // 4 "Not AA"}; // 5 /** * {@inheritDoc} */ @Override public String getTypeString() { return "f2"; } /** * {@inheritDoc} */ @Override public String[] getMarkerDataCategoricalValues() { return this.markerCategoricalValues; } /** * {@inheritDoc} */ @Override public String toString() { return "Intercross"; } }, /** * a back-cross */ BACK_CROSS { private final String[] markerCategoricalValues = new String[] { "AA", // 1 "AB"}; // 2 /** * {@inheritDoc} */ @Override public String getTypeString() { return "bc"; } /** * {@inheritDoc} */ @Override public String[] getMarkerDataCategoricalValues() { return this.markerCategoricalValues; } /** * {@inheritDoc} */ @Override public String toString() { return "Backcross"; } }, /** * a 4-way cross */ FOUR_WAY { private final String[] markerCategoricalValues = new String[] { "AC", // 1 "BC", // 2 "AD", // 3 "BD", // 4 "A, AC or AD", // 5 "B, BC or BD", // 6 "C, AC or BC", // 7 "D, AD or BD", // 8 "AC or BD", // 9 "AD or BC"}; // 10 /** * {@inheritDoc} */ @Override public String getTypeString() { return "4way"; } /** * {@inheritDoc} */ @Override public String[] getMarkerDataCategoricalValues() { return this.markerCategoricalValues; } /** * {@inheritDoc} */ @Override public String toString() { return "Four-Way Cross"; } }; /** * Get the R type string for this sub-type * @return * the type string */ public abstract String getTypeString(); /** * Get the marker data values for this sub-type. Please note that the * R values for marker data are from 1 to n, but these strings need * to be indexed from 0 to n-1 * @return * the ordered array of category strings */ public abstract String[] getMarkerDataCategoricalValues(); } /** * local copy of the phenotype data for this cross */ private NamedDataMatrix<Number> phenotypeData; /** * @see #getGenotypeData() */ private List<CrossChromosome> genotypeData; /** * @see #getQtlBasketMap() */ private Map<String, QtlBasket> qtlBasketMap; /** * @see #getCrossSubType() */ private CrossSubType crossSubType; private final Set<ScanOneResult> scanOneResults; private final Set<ScanTwoResult> scanTwoResults; private final Set<FitQtlResult> fitQtlResults; /** * for dealing with bean events */ private final PropertyChangeSupport propertyChangeSupport; /** * the phenotype sub-component of any cross */ private static final String PHENO_COMPONENT = "$pheno"; /** * These are phenotypes that we just assume are categorical even * if the R type is not categorical. See R documentation for * read.cross(...) for more information */ public static enum AssumedCategoricalPhenotype { /** * "pgm" */ PATERNAL_GRANDMOTHER { private final String[] categoryNames = new String[] { "(?x?)x(AxB)", "(?x?)x(BxA)"}; private final String columnHeader = "pgm"; /** * {@inheritDoc} */ @Override public String[] getCategoryNames() { return this.categoryNames; } /** * {@inheritDoc} */ @Override public String getColumnHeader() { return this.columnHeader; } }, /** * "sex" */ SEX { private final String[] categoryNames = new String[] { "female", "male"}; private final String columnHeader = "sex"; /** * {@inheritDoc} */ @Override public String[] getCategoryNames() { return this.categoryNames; } /** * {@inheritDoc} */ @Override public String getColumnHeader() { return this.columnHeader; } }; /** * The column header that is used for this phenotype * @return * the column header */ public abstract String getColumnHeader(); /** * Getter for the category names used for this enum * @see NamedCategoricalData#getCategoryNames() * @return * the category names */ public abstract String[] getCategoryNames(); /** * Get the categorical phenotype with the given header * @param columnHeader * the header * @return * the matching phenotype or null if there are no matches */ public static AssumedCategoricalPhenotype getCategoricalPhenotypeWithHeader(String columnHeader) { for(AssumedCategoricalPhenotype currPheno: AssumedCategoricalPhenotype.values()) { if(currPheno.getColumnHeader().equalsIgnoreCase(columnHeader)) { return currPheno; } } return null; } } /** * the property string we use for phenotype data when we fire change * events */ public static final String PHENOTYPE_DATA_PROPERTY_NAME = "phenotypeData"; /** * the property string we use for genotype data when we fire change * events */ public static final String GENOTYPE_DATA_PROPERTY_NAME = "genotypeData"; /** * Constructor for an R backed cross object * @param accessorExpressionString * the identifier string for this cross in R * @param rInterface * the R interface that has all of the real data for this cross */ public Cross( RInterface rInterface, String accessorExpressionString) { super(rInterface, accessorExpressionString); this.propertyChangeSupport = new PropertyChangeSupport(this); this.qtlBasketMap = Collections.synchronizedMap( new HashMap<String, QtlBasket>()); this.scanOneResults = Collections.synchronizedSet( new HashSet<ScanOneResult>()); this.scanTwoResults = Collections.synchronizedSet( new HashSet<ScanTwoResult>()); this.fitQtlResults = Collections.synchronizedSet( new HashSet<FitQtlResult>()); this.updateAll(); } /** * Add a property listener * @param listener * the listener * @see PropertyChangeSupport#addPropertyChangeListener(PropertyChangeListener) * @see #GENOTYPE_DATA_PROPERTY_NAME * @see #PHENOTYPE_DATA_PROPERTY_NAME */ public void addPropertyChangeListener(PropertyChangeListener listener) { this.propertyChangeSupport.addPropertyChangeListener(listener); } /** * Add a property listener * @param propertyName * the property to listen to * @param listener * the listener * @see PropertyChangeSupport#addPropertyChangeListener(String, PropertyChangeListener) * @see #GENOTYPE_DATA_PROPERTY_NAME * @see #PHENOTYPE_DATA_PROPERTY_NAME */ public void addPropertyChangeListener( String propertyName, PropertyChangeListener listener) { this.propertyChangeSupport.addPropertyChangeListener( propertyName, listener); } /** * Remove a property listener * @param listener * the listener * @see PropertyChangeSupport#removePropertyChangeListener(PropertyChangeListener) * @see #GENOTYPE_DATA_PROPERTY_NAME * @see #PHENOTYPE_DATA_PROPERTY_NAME */ public void removePropertyChangeListener( PropertyChangeListener listener) { this.propertyChangeSupport.removePropertyChangeListener(listener); } /** * Remove a property listener * @param propertyName * the property to stop listening to * @param listener * the listener * @see PropertyChangeSupport#removePropertyChangeListener(String, PropertyChangeListener) * @see #GENOTYPE_DATA_PROPERTY_NAME * @see #PHENOTYPE_DATA_PROPERTY_NAME */ public void removePropertyChangeListener( String propertyName, PropertyChangeListener listener) { this.propertyChangeSupport.removePropertyChangeListener( propertyName, listener); } /** * Call this method when all state data for this cross should be updated. */ private void updateAll() { // update the cross sub-type for(CrossSubType currSubType: CrossSubType.values()) { if(JRIUtilityFunctions.inheritsRClass( this, currSubType.getTypeString())) { this.crossSubType = currSubType; break; } } this.updatePhenotypeData(); this.updateGenotypeData(); } /** * Update the genotype data */ private void updateGenotypeData() { String chromosomeNamesCommandString = "names(" + this.getAccessorExpressionString() + CrossChromosome.GENO_COMPONENT + ")"; REXP chromosomeNamesRexp = this.getRInterface().evaluateCommand(new SilentRCommand( chromosomeNamesCommandString)); String[] chromosomeNames = chromosomeNamesRexp.asStringArray(); List<CrossChromosome> genotypeData = new ArrayList<CrossChromosome>(); for(String chromosomeName: chromosomeNames) { genotypeData.add(new CrossChromosome( this, chromosomeName)); } this.setGenotypeData(genotypeData); } /** * Set the genotype data * @param genotypeData * the new genotype data */ private void setGenotypeData(List<CrossChromosome> genotypeData) { List<CrossChromosome> oldGenotypeData = this.genotypeData; this.genotypeData = genotypeData; this.propertyChangeSupport.firePropertyChange( GENOTYPE_DATA_PROPERTY_NAME, oldGenotypeData, genotypeData); } /** * Get the index of the chromosome with the given name or -1 if we * can't find the index * @param chromosomeName * the chromosome name that we're looking for * @return * the index or -1 if we can't find it */ public int getIndexOfChromosomeNamed(String chromosomeName) { List<CrossChromosome> genoData = this.getGenotypeData(); for(int i = 0; i < genoData.size(); i++) { CrossChromosome currCrossChromosome = genoData.get(i); if(currCrossChromosome.getChromosomeName().equals(chromosomeName)) { return i; } } return -1; } /** * Getter for the genotype data * @return * the genotype data */ public List<CrossChromosome> getGenotypeData() { return this.genotypeData; } /** * Getter for the phenotype data * @return * the phenotype data */ public NamedDataMatrix<Number> getPhenotypeData() { return this.phenotypeData; } /** * Get the QTL baskets * @return * the QTL baskets */ public Map<String, QtlBasket> getQtlBasketMap() { return this.qtlBasketMap; } /** * Getter for the qtl baskets * @see #getQtlBasketMap() * @return * the QTL baskets */ public QtlBasket[] getQtlBaskets() { synchronized(this.qtlBasketMap) { QtlBasket[] baskets = new QtlBasket[this.qtlBasketMap.size()]; return this.qtlBasketMap.values().toArray(baskets); } } /** * Update the phenotype data */ public void updatePhenotypeData() { NamedDataMatrix<Number> newPhenoData = Cross.getPhenotypeDataForCross( this.getRInterface(), this.getAccessorExpressionString()); if(!ObjectUtil.areEqual(this.phenotypeData, newPhenoData)) { NamedDataMatrix<Number> oldPhenoData = this.phenotypeData; this.phenotypeData = newPhenoData; this.propertyChangeSupport.firePropertyChange( PHENOTYPE_DATA_PROPERTY_NAME, oldPhenoData, newPhenoData); } } /** * Getter for determining if "calc.genoprob" was run on this cross * @return * true if it was run */ public boolean getCalculateConditionalProbabilitiesWasUsed() { return Cross.methodWasUsed( this.getRInterface(), this.getAccessorExpressionString(), GenotypeProbabilityMethod.CALCULATE_CONDITIONAL_PROBABILITIES); } /** * Getter for determining if "sim.geno" was run on this cross * @return * true if it was run */ public boolean getSimulateGenotypeWasUsed() { return Cross.methodWasUsed( this.getRInterface(), this.getAccessorExpressionString(), GenotypeProbabilityMethod.SIMULATE_GENOTYPE); } /** * Determine if the given method was used on the given cross. * @param rInterface * the R interface to use * @param crossIdentifier * the identifier for the cross we're asking about * @param method * the method that we're testing for * @return * true iff the method was used */ public static boolean methodWasUsed( RInterface rInterface, String crossIdentifier, GenotypeProbabilityMethod method) { RCommand genoSubComponentsCommand = Cross.getGenotypeSubComponentsCommand( crossIdentifier, 0); REXP resultExpression = rInterface.evaluateCommand(genoSubComponentsCommand); String[] genoSubComponents = resultExpression.asStringArray(); for(String currGenoSubComponent: genoSubComponents) { if(currGenoSubComponent.equals(method.getGenoSubComponentName())) { return true; } } return false; } /** * Get the command string to fetch all genotype subcomponent names * @param crossIdentifier * the cross * @param chromosomeNumber * the chromosome that we want the names for (0 based index) * @return * the command needed to get the names */ private static RCommand getGenotypeSubComponentsCommand( String crossIdentifier, int chromosomeNumber) { return new SilentRCommand( "names(" + crossIdentifier + CrossChromosome.GENO_COMPONENT + "[[" + (chromosomeNumber + 1) + "]])"); } /** * For the given cross in R, get the phenotype data as a matrix * @param rInterface * the R interface to use * @param crossIdentifier * the identifier for the cross that we're interested in * @return * the phenotype data matrix */ public static NamedDataMatrix<Number> getPhenotypeDataForCross( RInterface rInterface, String crossIdentifier) { // // get all information after read.cross() to create the cross // // pheno data // REXP x = rInterface.evaluateCommand("names(" + crossName + "$pheno)"); // String[] phenoNames = rInterface.evaluateCommand("names(" + crossName + "$pheno)"). // asStringArray(); // int nphe = phenoNames.length; // // for (int i = 0; i < nphe; i++) { // rcmd = crossName + "$pheno$" + phenoNames[i]; // // // TODO make sure both conditions are unit tested // if (rInterface.evaluateCommand(rcmd).getType() == REXP.XT_ARRAY_DOUBLE) { // double[] phenoDouble = rInterface.evaluateCommand(rcmd).asDoubleArray(); // pheno = convertPheno(phenoDouble); // } // else if (rInterface.evaluateCommand(rcmd).getType() == REXP.XT_FACTOR) { // RFactor phenoFactor = rInterface.evaluateCommand(rcmd).asFactor(); // pheno = Tools.convertPheno(phenoFactor); // } // boolean isFactor = rInterface.evaluateCommand("is.factor(" + rcmd + ")").asBool(). // isTRUE(); // if (pd.getPhenoName().equalsIgnoreCase("sex") || pd.getPhenoName().equalsIgnoreCase("pgm")) // isFactor = true; // pd.setFactor(isFactor); // cross.addPhenoData(pd); // } String phenotypeCommand = crossIdentifier + PHENO_COMPONENT; REXP phenotypesNamesExpression = rInterface.evaluateCommand( new SilentRCommand("names(" + phenotypeCommand + ")")); String[] phenotypeNames = phenotypesNamesExpression.asStringArray(); List<NamedData<Number>> namedPhenotypeData = new ArrayList<NamedData<Number>>(phenotypeNames.length); for(int i = 0; i < phenotypeNames.length; i++) { String currPhenotypeCommand = phenotypeCommand + "$" + phenotypeNames[i]; REXP currPhenotypeExpression = rInterface.evaluateCommand( new SilentRCommand(currPhenotypeCommand)); // TODO: need to deal with pgm and sex special cases // TODO test all of these conditions if(currPhenotypeExpression.getType() == REXP.XT_FACTOR) { // TODO this should probably be a separate function // TODO are there any R N/A values that we need to deal // with here RFactor phenoFactor = currPhenotypeExpression.asFactor(); int numFactors = phenoFactor.size(); List<String> categoryNamesList = new ArrayList<String>(); Integer[] categoryData = new Integer[numFactors]; for(int j = 0; j < numFactors; j++) { String currPhenoStrVal; // This try/catch is a workaround for a JRI problem // reading NA factors try { currPhenoStrVal = phenoFactor.at(j); } catch (IndexOutOfBoundsException ex) { LOG.log(Level.FINE, "Caught index out of bounds. Assuming N/A phenotype"); currPhenoStrVal = null; } int currPhenoCatVal = categoryNamesList.indexOf( currPhenoStrVal); if(currPhenoCatVal == -1) { currPhenoCatVal = categoryNamesList.size(); categoryNamesList.add(currPhenoStrVal); } categoryData[j] = Integer.valueOf(currPhenoCatVal); } NamedCategoricalData currPhenotypeData = new NamedCategoricalData( phenotypeNames[i], categoryData, categoryNamesList.toArray( new String[categoryNamesList.size()])); namedPhenotypeData.add(currPhenotypeData); } else { AssumedCategoricalPhenotype matchingAssumedCategoricalPheno = AssumedCategoricalPhenotype.getCategoricalPhenotypeWithHeader( phenotypeNames[i]); if(matchingAssumedCategoricalPheno != null) { NamedCategoricalData currPhenotypeData = new NamedCategoricalData( phenotypeNames[i], JRIUtilityFunctions.extractIntegerValues( currPhenotypeExpression), matchingAssumedCategoricalPheno.getCategoryNames()); namedPhenotypeData.add(currPhenotypeData); } else if(currPhenotypeExpression.getType() == REXP.XT_ARRAY_DOUBLE) { Double[] currPhenotypeValues; { double[] currPhenotypePrimitiveValues = currPhenotypeExpression.asDoubleArray(); currPhenotypeValues = new Double[currPhenotypePrimitiveValues.length]; for(int j = 0; j < currPhenotypePrimitiveValues.length; j++) { if(Double.isNaN(currPhenotypePrimitiveValues[j])) { currPhenotypeValues[j] = null; } else { currPhenotypeValues[j] = Double.valueOf(currPhenotypePrimitiveValues[j]); } } } NamedRealData currPhenotypeData = new NamedRealData( phenotypeNames[i], currPhenotypeValues); namedPhenotypeData.add(currPhenotypeData); } else if(currPhenotypeExpression.getType() == REXP.XT_ARRAY_INT) { int[] currPhenotypeValues = currPhenotypeExpression.asIntArray(); NamedIntegerData currPhenotypeData = new NamedIntegerData( phenotypeNames[i], currPhenotypeValues); namedPhenotypeData.add(currPhenotypeData); } else { // don't know what to do with this one // TODO should probably throw an exception if this happens LOG.severe( "don't know how to translate expression of type: " + currPhenotypeExpression.getType()); } } } return new SimpleSelectableNamedDataMatrix<Number>( namedPhenotypeData); } /** * Getter for the cross sub-type * @return * the cross sub-type */ public CrossSubType getCrossSubType() { return this.crossSubType; } /** * Gets the number of individuals in this cross * @return * the number of individuals */ public int getNumberOfIndividuals() { String commandString = "nind(" + this.getAccessorExpressionString() + ")"; SilentRCommand silentCommand = new SilentRCommand(commandString); return this.getRInterface().evaluateCommand(silentCommand).asInt(); } /** * Get the number of chromosomes for this cross * @return * the number or chromosomes */ public int getNumberOfChromosomes() { String commandString = "nchr(" + this.getAccessorExpressionString() + ")"; SilentRCommand silentCommand = new SilentRCommand(commandString); return this.getRInterface().evaluateCommand(silentCommand).asInt(); } /** * Get the number of phenotypes for this cross * @return * the number of phenotypes */ public int getNumberOfPhenotypes() { String commandString = "nphe(" + this.getAccessorExpressionString() + ")"; SilentRCommand silentCommand = new SilentRCommand(commandString); return this.getRInterface().evaluateCommand(silentCommand).asInt(); } /** * Get the number of markers for this cross * @return * the number of markers */ public int[] getNumberOfMarkers() { String commandString = "nmar(" + this.getAccessorExpressionString() + ")"; SilentRCommand silentCommand = new SilentRCommand(commandString); return this.getRInterface().evaluateCommand(silentCommand).asIntArray(); } /** * Calculate error LOD values * @see #getErrorLodsExist() * @see CrossChromosome#getMarkerErrorLods() */ public void calculateErrorLods() { String errorLodComment = "calculating error LOD values"; String errorLodCommand = this.getAccessorExpressionString() + " <- calc.errorlod(" + this.getAccessorExpressionString() + ")"; this.getRInterface().insertComment(errorLodComment); this.getRInterface().evaluateCommandNoReturn(errorLodCommand); } /** * Get all scan one results that belong to this cross * @return * the scan results */ public Set<ScanOneResult> getScanOneResults() { // grab all scanone results List<RObject> allScanoneResultRObjects = JRIUtilityFunctions.getTopLevelObjectsOfType( this.getRInterface(), ScanOneResult.SCANONE_RESULT_TYPE_STRING); // filter out any scanone results that don't belong to this cross this.removeObjectsNotOwnedByThis(allScanoneResultRObjects); Set<ScanOneResult> matchingScanoneResults = new HashSet<ScanOneResult>(); for(RObject currScanoneRObject: allScanoneResultRObjects) { matchingScanoneResults.add(new ScanOneResult( currScanoneRObject.getRInterface(), currScanoneRObject.getAccessorExpressionString(), this)); } this.scanOneResults.retainAll(matchingScanoneResults); matchingScanoneResults.removeAll(this.scanOneResults); this.scanOneResults.addAll(matchingScanoneResults); // return a copy of the matches return new HashSet<ScanOneResult>(this.scanOneResults); } /** * Get the scantwo results * @return * the resutls */ public Set<ScanTwoResult> getScanTwoResults() { // grab all scantwo results List<RObject> allScantwoResultRObjects = JRIUtilityFunctions.getTopLevelObjectsOfType( this.getRInterface(), ScanTwoResult.SCANTWO_RESULT_TYPE_STRING); // filter out any scantwo results that don't belong to this cross this.removeObjectsNotOwnedByThis(allScantwoResultRObjects); Set<ScanTwoResult> matchingScantwoResults = new HashSet<ScanTwoResult>(); for(RObject currScantwoRObject: allScantwoResultRObjects) { matchingScantwoResults.add(new ScanTwoResult( currScantwoRObject.getRInterface(), currScantwoRObject.getAccessorExpressionString(), this)); } this.scanTwoResults.retainAll(matchingScantwoResults); matchingScantwoResults.removeAll(this.scanTwoResults); this.scanTwoResults.addAll(matchingScantwoResults); // return a copy of the matches return new HashSet<ScanTwoResult>(this.scanTwoResults); } /** * Getter for the fit results * @return * the fit results */ public Set<FitQtlResult> getFitQtlResults() { // grab all of the fit results List<RObject> fitQtlRObjects = JRIUtilityFunctions.getTopLevelObjectsOfType( this.getRInterface(), FitQtlResult.FIT_QTL_RESULT_TYPE_STRING); // filter out and fit's that don't belong to this cross this.removeObjectsNotOwnedByThis(fitQtlRObjects); Set<FitQtlResult> matchingFitQtlResults = new HashSet<FitQtlResult>(fitQtlRObjects.size()); for(RObject currFitRObject: fitQtlRObjects) { matchingFitQtlResults.add(new FitQtlResult( currFitRObject.getRInterface(), currFitRObject.getAccessorExpressionString(), this)); } this.fitQtlResults.retainAll(matchingFitQtlResults); matchingFitQtlResults.removeAll(this.fitQtlResults); this.fitQtlResults.addAll(matchingFitQtlResults); // return a copy of the matches return new HashSet<FitQtlResult>(this.fitQtlResults); } /** * This function determines whether or not the error lod values have * been calculated or not * @return * true iff the error lods have been calculated */ public boolean getErrorLodsExist() { if(this.genotypeData.size() == 0) { return false; } else { CrossChromosome firstChromosome = this.genotypeData.get(0); return firstChromosome.getErrorLodsExist(); } } /** * Get the data for "pgm" or "sex" if it's available * @param phenotype * the phenotype we're looking for * @return * the paternal grandmother phenotype, sex phenotype or null if the * data are not available */ public NamedCategoricalData getAssumedCategoricalPhenotype( AssumedCategoricalPhenotype phenotype) { String header = phenotype.getColumnHeader(); for(NamedData<Number> currData: this.phenotypeData.getNamedDataList()) { if(currData.getNameOfData().equalsIgnoreCase(header)) { if(currData instanceof NamedCategoricalData) { return (NamedCategoricalData)currData; } else { LOG.warning( currData.getNameOfData() + " was expected to " + "be categorical data but it is actually: " + currData.getClass().getName()); return null; } } } return null; } /** * {@inheritDoc} */ @Override public int hashCode() { return ObjectUtil.hashObject(this.getAccessorExpressionString()); } }