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