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 }