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 }