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