1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  package ubic.basecode.math.linearmodels;
21  
22  import org.apache.commons.lang3.StringUtils;
23  import org.jspecify.annotations.Nullable;
24  
25  import java.io.Serializable;
26  import java.util.*;
27  import java.util.stream.Collectors;
28  
29  
30  
31  
32  
33  
34  class GenericAnovaResultImpl implements Serializable, GenericAnovaResult {
35  
36      private static final long serialVersionUID = 1L;
37  
38      private final String key;
39      private final Map<TreeSet<String>, AnovaEffect> interactionEffects = new LinkedHashMap<>();
40      private final Map<String, AnovaEffect> mainEffects = new LinkedHashMap<>();
41      @Nullable
42      private final AnovaEffect residual;
43  
44      public GenericAnovaResultImpl( String key, Collection<AnovaEffect> effects ) {
45          this.key = key;
46          AnovaEffect residual = null;
47          for ( AnovaEffect ae : effects ) {
48              String termLabel = ae.getEffectName();
49              if ( ae.isInteraction() ) { 
50                  interactionEffects.put( new TreeSet<>( Arrays.asList( StringUtils.split( termLabel, ":" ) ) ), ae );
51              } else if ( ae.isResiduals() ) {
52                  residual = ae;
53              } else {
54                  mainEffects.put( termLabel, ae );
55              }
56          }
57          this.residual = residual;
58      }
59  
60      @Override
61      public String getKey() {
62          return key;
63      }
64  
65      @Override
66      public boolean hasResiduals() {
67          return residual != null;
68      }
69  
70      @Override
71      public double getResidualsDof() {
72          return residual != null ? residual.getDof() : Double.NaN;
73      }
74  
75      @Override
76      public double getResidualsFStat() {
77          return residual != null ? residual.getFStat() : Double.NaN;
78      }
79  
80      @Override
81      public double getResidualsPValue() {
82          return residual != null ? residual.getPValue() : Double.NaN;
83      }
84  
85      @Override
86      public double getInteractionEffectDof() {
87          if ( interactionEffects.size() > 1 ) {
88              throw new IllegalStateException( "Must specify which interaction" );
89          }
90          if ( interactionEffects.isEmpty() ) {
91              return Double.NaN;
92          }
93          return interactionEffects.values().iterator().next().getDof();
94      }
95  
96      @Override
97      public double getInteractionEffectFStat() {
98          if ( interactionEffects.size() > 1 ) {
99              throw new IllegalStateException( "Must specify which interaction" );
100         }
101         if ( interactionEffects.isEmpty() ) {
102             return Double.NaN;
103         }
104         return interactionEffects.values().iterator().next().getFStat();
105     }
106 
107     @Override
108     public double getInteractionEffectPValue() {
109         if ( interactionEffects.size() > 1 ) {
110             throw new IllegalStateException( "Must specify which interaction" );
111         }
112         if ( interactionEffects.isEmpty() ) {
113             return Double.NaN;
114         }
115         return interactionEffects.values().iterator().next().getPValue();
116     }
117 
118     @Override
119     public Collection<String[]> getInteractionEffectFactorNames() {
120         return interactionEffects.keySet().stream()
121             .map( fn -> fn.toArray( new String[0] ) )
122             .collect( Collectors.toList() );
123     }
124 
125     @Override
126     public double getInteractionEffectDof( String... factorNames ) {
127         if ( interactionEffects.isEmpty() ) {
128             return Double.NaN;
129         }
130         Set<String> interactionFactor = new TreeSet<>( Arrays.asList( factorNames ) );
131         if ( !interactionEffects.containsKey( interactionFactor ) ) {
132             return Double.NaN;
133         }
134         return interactionEffects.get( interactionFactor ).getDof();
135     }
136 
137     @Override
138     public double getInteractionEffectFStat( String... factorNames ) {
139         if ( interactionEffects.isEmpty() ) {
140             return Double.NaN;
141         }
142         TreeSet<String> interactionFactor = new TreeSet<>( Arrays.asList( factorNames ) );
143         if ( !interactionEffects.containsKey( interactionFactor ) ) {
144             return Double.NaN;
145         }
146         return interactionEffects.get( interactionFactor ).getFStat();
147     }
148 
149     @Override
150     public double getInteractionEffectPValue( String... factorNames ) {
151         if ( interactionEffects.isEmpty() ) {
152             return Double.NaN;
153         }
154         TreeSet<String> interactionFactor = new TreeSet<>( Arrays.asList( factorNames ) );
155         if ( !interactionEffects.containsKey( interactionFactor ) ) {
156             return Double.NaN;
157         }
158         return interactionEffects.get( interactionFactor ).getPValue();
159     }
160 
161     @Override
162     public double getMainEffectDof( String factorName ) {
163         if ( mainEffects.get( factorName ) == null ) {
164             return Double.NaN;
165         }
166         return mainEffects.get( factorName ).getDof();
167     }
168 
169     @Override
170     public double getMainEffectFStat( String factorName ) {
171         if ( mainEffects.get( factorName ) == null ) {
172             return Double.NaN;
173         }
174         return mainEffects.get( factorName ).getFStat();
175     }
176 
177     
178 
179 
180     @Override
181     public Collection<String> getMainEffectFactorNames() {
182         return mainEffects.keySet();
183     }
184 
185     @Override
186     public double getMainEffectPValue( String factorName ) {
187         if ( mainEffects.get( factorName ) == null ) {
188             return Double.NaN;
189         }
190         return mainEffects.get( factorName ).getPValue();
191     }
192 
193     @Override
194     public boolean hasInteractions() {
195         return !interactionEffects.isEmpty();
196     }
197 
198     @Override
199     public int hashCode() {
200         final int prime = 31;
201         int result = 1;
202         result = prime * result + ( ( key == null ) ? 0 : key.hashCode() );
203         return result;
204     }
205 
206     @Override
207     public boolean equals( Object obj ) {
208         if ( this == obj ) return true;
209         if ( obj == null ) return false;
210         if ( getClass() != obj.getClass() ) return false;
211         GenericAnovaResultImpl other = ( GenericAnovaResultImpl ) obj;
212         if ( key == null ) {
213             if ( other.key != null ) return false;
214         } else if ( !key.equals( other.key ) ) return false;
215         return true;
216     }
217 
218     @Override
219     public String toString() {
220         StringBuilder buf = new StringBuilder();
221 
222         buf.append( "ANOVA table " ).append( this.getKey() ).append( " \n" );
223 
224         buf.append( StringUtils.leftPad( "\t", 10 ) ).append( "Df\tSSq\tMSq\tF\tP\n" );
225 
226         for ( String me : this.getMainEffectFactorNames() ) {
227             if ( me.equals( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME ) ) {
228                 continue;
229             }
230             AnovaEffect a = mainEffects.get( me );
231             buf.append( a ).append( "\n" );
232         }
233 
234         if ( hasInteractions() ) {
235             for ( TreeSet<String> ifa : interactionEffects.keySet() ) {
236                 AnovaEffect a = this.interactionEffects.get( ifa );
237                 buf.append( a ).append( "\n" );
238             }
239         }
240 
241         buf.append( residual ).append( "\n" );
242 
243         return buf.toString();
244     }
245 }