View Javadoc
1   /*
2    * The baseCode project
3    *
4    * Copyright (c) 2010 University of British Columbia
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *       http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   *
18   */
19  
20  package ubic.basecode.math.linearmodels;
21  
22  import java.io.Serializable;
23  import java.util.Collection;
24  import java.util.HashMap;
25  import java.util.LinkedHashMap;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.TreeSet;
29  
30  import org.apache.commons.lang3.StringUtils;
31  import org.rosuda.REngine.REXP;
32  import org.rosuda.REngine.REXPMismatchException;
33  
34  import ubic.basecode.util.r.type.AnovaEffect;
35  import ubic.basecode.util.r.type.AnovaResult;
36  
37  /**
38   * A generic representation of an R-style ANOVA table, including interactions.
39   *
40   * @author paul
41   */
42  public class GenericAnovaResult extends AnovaResult implements Serializable {
43  
44      /**
45       * Represents a set of factors in an interaction term.
46       */
47      private static class InteractionFactor {
48  
49          // used only in hashcode; not a great idea
50          private static final String SEPARATOR = "_x_x_";
51  
52          // Treeset - keep them sorted to be consistent across instances.
53          private Set<String> factorNames = new TreeSet<>();
54  
55          public InteractionFactor( String... factorNames ) {
56              for ( String f : factorNames ) {
57                  this.factorNames.add( f );
58              }
59          }
60  
61          /*
62           * (non-Javadoc)
63           *
64           * @see java.lang.Object#equals(java.lang.Object)
65           */
66          @Override
67          public boolean equals( Object obj ) {
68  
69              if ( !( obj instanceof InteractionFactor ) ) return false;
70  
71              return ( ( InteractionFactor ) obj ).factorNames.size() == this.factorNames.size()
72                      && ( ( InteractionFactor ) obj ).factorNames.containsAll( this.factorNames );
73  
74          }
75  
76          /*
77           * (non-Javadoc)
78           *
79           * @see java.lang.Object#hashCode()
80           */
81          @Override
82          public int hashCode() {
83              return StringUtils.join( factorNames, SEPARATOR ).hashCode();
84          }
85      }
86  
87      private static final long serialVersionUID = 1L;
88  
89      private Map<InteractionFactor, AnovaEffect> interactionEffects = new HashMap<>();
90  
91      private Map<String, AnovaEffect> mainEffects = new LinkedHashMap<>();
92  
93      private AnovaEffect residual;
94  
95      /**
96       * @param effects
97       */
98      public GenericAnovaResult( Collection<AnovaEffect> effects ) {
99          for ( AnovaEffect ae : effects ) {
100             String termLabel = ae.getEffectName();
101             boolean isInteraction = ae.isInteraction();
102 
103             if ( isInteraction ) { // kind of lame way to detect interaction rows.
104                 interactionEffects.put( new InteractionFactor( StringUtils.split( termLabel, ":" ) ), ae );
105             } else if ( termLabel.equals( "Residual" ) ) {
106                 this.residualDf = ae.getDegreesOfFreedom();
107                 this.residual = ae;
108             } else {
109                 mainEffects.put( termLabel, ae );
110             }
111         }
112     }
113 
114     /**
115      * @param rAnovaTable from R call to 'anova(...)'
116      */
117     public GenericAnovaResult( REXP rAnovaTable ) {
118 
119         try {
120 
121             String[] names = rAnovaTable.getAttribute( "row.names" ).asStrings();
122             double[] pvs = rAnovaTable.asList().at( "Pr(>F)" ).asDoubles();
123             double[] dfs = rAnovaTable.asList().at( "Df" ).asDoubles();
124             double[] fs = rAnovaTable.asList().at( "F value" ).asDoubles();
125 
126             double[] ssq = rAnovaTable.asList().at( "Sum Sq" ).asDoubles();
127 
128             for ( int i = 0; i < pvs.length; i++ ) {
129                 String termLabel = names[i];
130                 boolean isInteraction = termLabel.contains( ":" );
131 
132                 AnovaEffectvaEffect.html#AnovaEffect">AnovaEffect ae = new AnovaEffect( termLabel, pvs[i], fs[i], dfs[i], ssq[i], isInteraction );
133 
134                 if ( isInteraction ) { // kind of lame way to detect interaction rows.
135                     interactionEffects.put( new InteractionFactor( StringUtils.split( termLabel, ":" ) ), ae );
136                 } else {
137                     mainEffects.put( termLabel, ae );
138                 }
139             }
140 
141         } catch ( REXPMismatchException e ) {
142             throw new RuntimeException( e );
143         }
144 
145     }
146 
147     public Double getInteractionEffectF() {
148         if ( interactionEffects.size() > 1 ) {
149             throw new IllegalArgumentException( "Must specify which interaction" );
150         }
151         if ( interactionEffects.isEmpty() ) {
152             return null;
153         }
154         return interactionEffects.values().iterator().next().getFStatistic();
155     }
156 
157     public Double getInteractionEffectP() {
158         if ( interactionEffects.size() > 1 ) {
159             throw new IllegalArgumentException( "Must specify which interaction" );
160         }
161         if ( interactionEffects.isEmpty() ) {
162             return null;
163         }
164         return interactionEffects.values().iterator().next().getPValue();
165     }
166 
167     /**
168      * @param factorNames e.g. f,g
169      * @return
170      */
171     public Double getInteractionEffectP( String... factorNames ) {
172         InteractionFactor interactionFactor = new InteractionFactor( factorNames );
173         if ( interactionEffects.isEmpty() ) {
174             return null;
175         }
176         if ( !interactionEffects.containsKey( interactionFactor ) ) {
177             return Double.NaN;
178         }
179         return interactionEffects.get( interactionFactor ).getPValue();
180     }
181 
182     public Double getMainEffectDof( String factorName ) {
183         if ( mainEffects.get( factorName ) == null ) {
184             return null;
185         }
186         return mainEffects.get( factorName ).getDegreesOfFreedom();
187     }
188 
189     public Double getMainEffectF( String factorName ) {
190         if ( mainEffects.get( factorName ) == null ) {
191             return Double.NaN;
192         }
193         return mainEffects.get( factorName ).getFStatistic();
194     }
195 
196     /**
197      * @return names of main effects factors like 'f', 'g'.
198      */
199     public Collection<String> getMainEffectFactorNames() {
200         return mainEffects.keySet();
201     }
202 
203     public Double getMainEffectP( String factorName ) {
204         if ( mainEffects.get( factorName ) == null ) {
205             return Double.NaN;
206         }
207         return mainEffects.get( factorName ).getPValue();
208     }
209 
210     public boolean hasInteractions() {
211         return !interactionEffects.isEmpty();
212     }
213 
214     @Override
215     public String toString() {
216         StringBuilder buf = new StringBuilder();
217 
218         buf.append( "ANOVA table " + this.getKey() + " \n" );
219 
220         buf.append( StringUtils.leftPad( "\t", 10 ) + "Df\tSSq\tMSq\tF\tP\n" );
221 
222         for ( String me : this.getMainEffectFactorNames() ) {
223             if ( me.equals( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME ) ) {
224                 continue;
225             }
226             AnovaEffect a = mainEffects.get( me );
227             buf.append( a + "\n" );
228         }
229 
230         if ( hasInteractions() ) {
231             for ( InteractionFactor ifa : interactionEffects.keySet() ) {
232                 AnovaEffect a = this.interactionEffects.get( ifa );
233                 buf.append( a + "\n" );
234             }
235         }
236 
237         buf.append( residual + "\n" );
238 
239         return buf.toString();
240     }
241 }