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  package ubic.basecode.math.linearmodels;
20  
21  import org.apache.commons.lang3.StringUtils;
22  import org.apache.commons.math3.distribution.FDistribution;
23  import ubic.basecode.dataStructure.matrix.DoubleMatrix;
24  
25  import javax.annotation.Nullable;
26  import java.util.Collection;
27  import java.util.HashMap;
28  import java.util.List;
29  import java.util.Map;
30  
31  /**
32   * Represents the results of a linear model analysis.
33   *
34   * @author paul
35   */
36  class LinearModelSummaryImpl implements LinearModelSummary {
37  
38      private static final long serialVersionUID = 1L;
39  
40      /**
41       * Name for this summary e.g. probe identifier
42       */
43      private final String key;
44      private final double adjRSquared;
45      @Nullable
46      private GenericAnovaResult anovaResult;
47      private final double[] coefficients;
48      private final DoubleMatrix<String, String> contrastCoefficients;
49      private final double denominatorDof;
50      /**
51       * AKA Qty
52       */
53      private final double[] effects;
54      private final List<String> factorNames;
55      private final double fStat;
56      private final double numeratorDof;
57      /**
58       * Only if ebayes has been applied
59       */
60      private final double priorDof;
61      private final double[] residuals;
62      private final double rSquared;
63      /**
64       * True = ebayes has been applied
65       */
66      private final boolean shrunken;
67      private final double sigma;
68      private final double[] stdevUnscaled;
69      private final Map<String, Collection<String>> term2CoefficientNames;
70  
71      @Nullable
72      private Double overallPValue = null;
73  
74      /**
75       * Construct an empty summary. Use for model fits that fail due to 0 degrees of freedom, etc.
76       */
77      public LinearModelSummaryImpl( String key ) {
78          this.key = key;
79          this.stdevUnscaled = null;
80          this.adjRSquared = Double.NaN;
81          this.coefficients = null;
82          this.effects = null;
83          this.sigma = Double.NaN;
84          this.priorDof = Double.NaN;
85          this.numeratorDof = Double.NaN;
86          this.fStat = Double.NaN;
87          this.shrunken = false;
88          this.rSquared = Double.NaN;
89          this.residuals = null;
90          this.factorNames = null;
91          this.denominatorDof = Double.NaN;
92          this.contrastCoefficients = null;
93          this.term2CoefficientNames = null;
94      }
95  
96      public LinearModelSummaryImpl( String k, double[] coefficients, double[] residuals, List<String> terms,
97          DoubleMatrix<String, String> contrastCoefficients, double[] effects, double[] stdevUnscaled, double rsquared,
98          double adjRsquared, double fstat, double ndof, double ddof, @Nullable GenericAnovaResult anovaResult,
99          double sigma, boolean isShrunken, double priorDof ) {
100         if ( anovaResult != null && !anovaResult.getKey().equals( k ) ) {
101             throw new IllegalArgumentException( "Keys of ANOVA and holding LM must match" );
102         }
103         this.residuals = residuals;
104         this.coefficients = coefficients;
105         this.contrastCoefficients = contrastCoefficients;
106         this.rSquared = rsquared;
107         this.adjRSquared = adjRsquared;
108         this.fStat = fstat;
109         this.numeratorDof = ndof;
110         this.denominatorDof = ddof;
111         this.key = k;
112         this.anovaResult = anovaResult;
113         this.effects = effects;
114         this.stdevUnscaled = stdevUnscaled;
115         this.factorNames = terms;
116         this.sigma = sigma;
117         this.shrunken = isShrunken;
118         this.priorDof = priorDof;
119         this.term2CoefficientNames = LinearModelSummaryUtils.createTerm2CoefficientNames( factorNames, contrastCoefficients );
120     }
121 
122     @Override
123     public double getAdjRSquared() {
124         return adjRSquared;
125     }
126 
127     @Override
128     public GenericAnovaResult getAnova() {
129         return this.anovaResult;
130     }
131 
132     @Override
133     public double[] getCoefficients() {
134         return coefficients;
135     }
136 
137     @Override
138     public DoubleMatrix<String, String> getContrastCoefficients() {
139         return contrastCoefficients;
140     }
141 
142     @Override
143     public Map<String, Double> getContrastCoefficients( String factorName ) {
144         return getContrastAttribute( factorName, "Estimate" );
145     }
146 
147     @Override
148     public Map<String, Double> getContrastCoefficientStderr( String factorName ) {
149         return getContrastAttribute( factorName, "Std. Error" );
150     }
151 
152     @Override
153     public Map<String, Double> getContrastPValues( String factorName ) {
154         return getContrastAttribute( factorName, "Pr(>|t|)" );
155     }
156 
157     @Override
158     public Map<String, Double> getContrastTStats( String factorName ) {
159         return getContrastAttribute( factorName, "t value" );
160     }
161 
162     private Map<String, Double> getContrastAttribute( String factorName, String attributeName ) {
163         Collection<String> terms = term2CoefficientNames.get( factorName );
164         if ( terms == null ) return null;
165         Map<String, Double> results = new HashMap<>();
166         for ( String term : terms ) {
167             results.put( term, contrastCoefficients.getByKeys( term, attributeName ) );
168         }
169         return results;
170     }
171 
172     @Override
173     public double[] getEffects() {
174         return this.effects;
175     }
176 
177     @Override
178     public double getFStat() {
179         return this.fStat;
180     }
181 
182     @Override
183     public List<String> getFactorNames() {
184         return factorNames;
185     }
186 
187     @Override
188     public double getInterceptCoefficient() {
189         if ( contrastCoefficients != null ) {
190             if ( contrastCoefficients.hasRow( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME ) ) {
191                 return contrastCoefficients.getByKeys( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME, "Estimate" );
192             } else if ( contrastCoefficients.rows() == 1 ) {
193                 /*
194                  * This is a bit of a kludge. When we use lm.fit instead of lm, we end up with a somewhat screwy
195                  * coefficent matrix in the case of one-sample ttest, and R put in x1 (I think it starts as 1 and it
196                  * prepends the x).
197                  */
198                 assert "x1".equals( contrastCoefficients.getRowName( 0 ) );
199                 return contrastCoefficients.getByKeys( contrastCoefficients.getRowName( 0 ), "Estimate" );
200             }
201         }
202 
203         return Double.NaN;
204     }
205 
206     @Override
207     public double getInterceptPValue() {
208         if ( contrastCoefficients != null ) {
209             if ( contrastCoefficients.hasRow( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME ) ) {
210                 return contrastCoefficients.getByKeys( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME, "Pr(>|t|)" );
211             } else if ( contrastCoefficients.rows() == 1 ) {
212                 /*
213                  * This is a bit of a kludge. When we use lm.fit instead of lm, we end up with a somewhat screwy
214                  * coefficent matrix in the case of one-sample ttest, and R put in x1 (I think it starts as 1 and it
215                  * prepends the x).
216                  */
217                 assert "x1".equals( contrastCoefficients.getRowName( 0 ) );
218                 return contrastCoefficients.getByKeys( contrastCoefficients.getRowName( 0 ), "Pr(>|t|)" );
219             }
220         }
221         return Double.NaN;
222     }
223 
224     @Override
225     public double getInterceptTStat() {
226         if ( contrastCoefficients != null ) {
227             if ( contrastCoefficients.hasRow( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME ) ) {
228                 return contrastCoefficients.getByKeys( LinearModelSummary.INTERCEPT_COEFFICIENT_NAME, "t value" );
229             } else if ( contrastCoefficients.rows() == 1 ) {
230                 /*
231                  * This is a bit of a kludge. When we use lm.fit instead of lm, we end up with a somewhat screwy
232                  * coefficent matrix in the case of one-sample ttest, and R put in x1 (I think it starts as 1 and it
233                  * prepends the x).
234                  */
235                 assert "x1".equals( contrastCoefficients.getRowName( 0 ) );
236                 return contrastCoefficients.getByKeys( contrastCoefficients.getRowName( 0 ), "t value" );
237             }
238         }
239 
240         return Double.NaN;
241     }
242 
243     public String getKey() {
244         return key;
245     }
246 
247     public double getMainEffectPValue( String factorName ) {
248         if ( anovaResult == null ) return Double.NaN;
249         return anovaResult.getMainEffectPValue( factorName );
250     }
251 
252     @Override
253     public double getNumeratorDof() {
254         return this.numeratorDof;
255     }
256 
257     @Override
258     public double getOverallPValue() {
259         if ( overallPValue == null ) {
260             if ( Double.isNaN( numeratorDof ) || Double.isNaN( denominatorDof ) || numeratorDof == 0 || denominatorDof == 0 ) {
261                 overallPValue = Double.NaN;
262             } else {
263                 FDistribution f = new FDistribution( numeratorDof, denominatorDof );
264                 overallPValue = 1.0 - f.cumulativeProbability( this.getFStat() );
265             }
266         }
267         return overallPValue;
268     }
269 
270     @Override
271     public double getPriorDof() {
272         return priorDof;
273     }
274 
275     @Override
276     public double getResidualsDof() {
277         return this.denominatorDof;
278     }
279 
280     @Override
281     public double[] getResiduals() {
282         return residuals;
283     }
284 
285     @Override
286     public double getRSquared() {
287         return rSquared;
288     }
289 
290     @Override
291     public double getSigma() {
292         return sigma;
293     }
294 
295     @Override
296     public double[] getStdevUnscaled() {
297         return stdevUnscaled;
298     }
299 
300     @Override
301     public boolean isBaseline( String factorValueName ) {
302         return !contrastCoefficients.hasRow( factorValueName );
303     }
304 
305     @Override
306     public boolean isShrunken() {
307         return shrunken;
308     }
309 
310     public void setAnova( GenericAnovaResult genericAnovaResult ) {
311         this.anovaResult = genericAnovaResult;
312     }
313 
314     @Override
315     public String toString() {
316         StringBuilder buf = new StringBuilder();
317         if ( StringUtils.isNotBlank( this.key ) ) {
318             buf.append( this.key ).append( "\n" );
319         }
320         buf.append( "F=" ).append( String.format( "%.2f", this.fStat ) ).append( " Rsquare=" ).append( String.format( "%.2f", this.rSquared ) ).append( "\n" );
321 
322         buf.append( "Residuals:\n" );
323         if ( residuals != null ) {
324             for ( double d : residuals ) {
325                 buf.append( String.format( "%.2f ", d ) );
326             }
327         } else {
328             buf.append( "Residuals are null" );
329         }
330 
331         buf.append( "\n\nCoefficients:\n" ).append( contrastCoefficients ).append( "\n" );
332         if ( this.anovaResult != null ) {
333             buf.append( this.anovaResult ).append( "\n" );
334         }
335 
336         return buf.toString();
337     }
338 
339 }