r2rs_stats/regression/glm/base.rs
1// "Whatever you do, work at it with all your heart, as working for the Lord,
2// not for human masters, since you know that you will receive an inheritance
3// from the Lord as a reward. It is the Lord Christ you are serving."
4// (Col 3:23-24)
5
6use std::{
7 fmt::{Display, Formatter},
8 marker::PhantomData,
9};
10
11use nalgebra::DMatrix;
12use r2rs_base::traits::{QuantileType, StatisticalSlice};
13use strafe_trait::{Model, Statistic, StatisticalEstimate, StatisticalTest};
14use strafe_type::{Alpha64, DisplayTable, FloatConstraint, ModelMatrix};
15
16use crate::regression::glm::{family::core::Family, link::core::Link};
17
18/// Fitting Generalized Linear Models
19///
20/// ## Description:
21///
22/// ‘glm’ is used to fit generalized linear models, specified by
23/// giving a symbolic description of the linear predictor and a
24/// description of the error distribution.
25///
26/// ## Usage:
27///
28/// glm(formula, family = gaussian, data, weights, subset,
29/// na.action, start = NULL, etastart, mustart, offset,
30/// control = list(...), model = TRUE, method = "glm.fit",
31/// x = FALSE, y = TRUE, singular.ok = TRUE, contrasts = NULL, ...)
32///
33/// glm.fit(x, y, weights = rep.int(1, nobs),
34/// start = NULL, etastart = NULL, mustart = NULL,
35/// offset = rep.int(0, nobs), family = gaussian(),
36/// control = list(), intercept = TRUE, singular.ok = TRUE)
37///
38/// ## S3 method for class 'glm'
39/// weights(object, type = c("prior", "working"), ...)
40///
41/// ## Arguments:
42///
43/// * formula: an object of class ‘"formula"’ (or one that can be coerced to
44/// that class): a symbolic description of the model to be
45/// fitted. The details of model specification are given under
46/// ‘Details’.
47/// * family: a description of the error distribution and link function to
48/// be used in the model. For ‘glm’ this can be a character
49/// string naming a family function, a family function or the
50/// result of a call to a family function. For ‘glm.fit’ only
51/// the third option is supported. (See ‘family’ for details of
52/// family functions.)
53/// * data: an optional data frame, list or environment (or object
54/// coercible by ‘as.data.frame’ to a data frame) containing the
55/// variables in the model. If not found in ‘data’, the
56/// variables are taken from ‘environment(formula)’, typically
57/// the environment from which ‘glm’ is called.
58/// * weights: an optional vector of ‘prior weights’ to be used in the
59/// fitting process. Should be ‘NULL’ or a numeric vector.
60/// * subset: an optional vector specifying a subset of observations to be
61/// used in the fitting process.
62/// * na.action: a function which indicates what should happen when the data
63/// contain ‘NA’s. The default is set by the ‘na.action’ setting
64/// of ‘options’, and is ‘na.fail’ if that is unset. The
65/// ‘factory-fresh’ default is ‘na.omit’. Another possible value
66/// is ‘NULL’, no action. Value ‘na.exclude’ can be useful.
67/// * start: starting values for the parameters in the linear predictor.
68/// * etastart: starting values for the linear predictor.
69/// * mustart: starting values for the vector of means.
70/// * offset: this can be used to specify an _a priori_ known component to
71/// be included in the linear predictor during fitting. This
72/// should be ‘NULL’ or a numeric vector of length equal to the
73/// number of cases. One or more ‘offset’ terms can be included
74/// in the formula instead or as well, and if more than one is
75/// specified their sum is used. See ‘model.offset’.
76/// * control: a list of parameters for controlling the fitting process.
77/// For ‘glm.fit’ this is passed to ‘glm.control’.
78/// * model: a logical value indicating whether _model frame_ should be
79/// included as a component of the returned value.
80/// * method: the method to be used in fitting the model. The default
81/// method ‘"glm.fit"’ uses iteratively reweighted least squares
82/// (IWLS): the alternative ‘"model.frame"’ returns the model
83/// frame and does no fitting.
84///
85/// User-supplied fitting functions can be supplied either as a
86/// function or a character string naming a function, with a
87/// function which takes the same arguments as ‘glm.fit’. If
88/// specified as a character string it is looked up from within
89/// the ‘stats’ namespace.
90///
91/// * x, y: For ‘glm’: logical values indicating whether the response
92/// vector and model matrix used in the fitting process should be
93/// returned as components of the returned value.
94///
95/// For ‘glm.fit’: ‘x’ is a design matrix of dimension ‘n * p’,
96/// and ‘y’ is a vector of observations of length ‘n’.
97///
98/// * singular.ok: logical; if ‘FALSE’ a singular fit is an error.
99/// * contrasts: an optional list. See the ‘contrasts.arg’ of
100/// ‘ model.matrix.default’.
101/// * intercept: logical. Should an intercept be included in the _null_
102/// model?
103/// * object: an object inheriting from class ‘"glm"’.
104/// * type: character, partial matching allowed. Type of weights to
105/// extract from the fitted model object. Can be abbreviated.
106/// * ...: For ‘glm’: arguments to be used to form the default ‘control’
107/// argument if it is not supplied directly.
108///
109/// For ‘weights’: further arguments passed to or from other
110/// methods.
111///
112/// ## Details:
113///
114/// A typical predictor has the form ‘response ~ terms’ where
115/// ‘response’ is the (numeric) response vector and ‘terms’ is a
116/// series of terms which specifies a linear predictor for ‘response’.
117/// For ‘binomial’ and ‘quasibinomial’ families the response can also
118/// be specified as a ‘factor’ (when the first level denotes failure
119/// and all others success) or as a two-column matrix with the columns
120/// giving the numbers of successes and failures. A terms
121/// specification of the form ‘first + second’ indicates all the terms
122/// in ‘first’ together with all the terms in ‘second’ with any
123/// duplicates removed.
124///
125/// A specification of the form ‘first:second’ indicates the set of
126/// terms obtained by taking the interactions of all terms in ‘first’
127/// with all terms in ‘second’. The specification ‘first*second’
128/// indicates the _cross_ of ‘first’ and ‘second’. This is the same
129/// as ‘first + second + first:second’.
130///
131/// The terms in the formula will be re-ordered so that main effects
132/// come first, followed by the interactions, all second-order, all
133/// third-order and so on: to avoid this pass a ‘terms’ object as the
134/// formula.
135///
136/// Non-‘NULL’ ‘weights’ can be used to indicate that different
137/// observations have different dispersions (with the values in
138/// ‘weights’ being inversely proportional to the dispersions); or
139/// equivalently, when the elements of ‘weights’ are positive integers
140/// w_i, that each response y_i is the mean of w_i unit-weight
141/// observations. For a binomial GLM prior weights are used to give
142/// the number of trials when the response is the proportion of
143/// successes: they would rarely be used for a Poisson GLM.
144///
145/// ‘glm.fit’ is the workhorse function: it is not normally called
146/// directly but can be more efficient where the response vector,
147/// design matrix and family have already been calculated.
148///
149/// If more than one of ‘etastart’, ‘start’ and ‘mustart’ is
150/// specified, the first in the list will be used. It is often
151/// advisable to supply starting values for a ‘quasi’ family, and also
152/// for families with unusual links such as ‘gaussian("log")’.
153///
154/// All of ‘weights’, ‘subset’, ‘offset’, ‘etastart’ and ‘mustart’ are
155/// evaluated in the same way as variables in ‘formula’, that is first
156/// in ‘data’ and then in the environment of ‘formula’.
157///
158/// For the background to warning messages about ‘fitted probabilities
159/// numerically 0 or 1 occurred’ for binomial GLMs, see Venables &
160/// Ripley (2002, pp. 197-8).
161///
162/// ## Value:
163///
164/// ‘glm’ returns an object of class inheriting from ‘"glm"’ which
165/// inherits from the class ‘"lm"’. See later in this section. If a
166/// non-standard ‘method’ is used, the object will also inherit from
167/// the class (if any) returned by that function.
168///
169/// The function ‘summary’ (i.e., ‘summary.glm’) can be used to obtain
170/// or print a summary of the results and the function ‘anova’ (i.e.,
171/// ‘anova.glm’) to produce an analysis of variance table.
172///
173/// The generic accessor functions ‘coefficients’, ‘effects’,
174/// ‘fitted.values’ and ‘residuals’ can be used to extract various
175/// useful features of the value returned by ‘glm’.
176///
177/// ‘weights’ extracts a vector of weights, one for each case in the
178/// fit (after subsetting and ‘na.action’).
179///
180/// An object of class ‘"glm"’ is a list containing at least the
181/// following components:
182///
183/// * coefficients: a named vector of coefficients
184/// * residuals: the _working_ residuals, that is the residuals in the final
185/// iteration of the IWLS fit. Since cases with zero weights are
186/// omitted, their working residuals are ‘NA’.
187/// * fitted.values: the fitted mean values, obtained by transforming the
188/// linear predictors by the inverse of the link function.
189/// * rank: the numeric rank of the fitted linear model.
190/// * family: the ‘family’ object used.
191/// * linear.predictors: the linear fit on link scale.
192/// * deviance: up to a constant, minus twice the maximized log-likelihood.
193/// Where sensible, the constant is chosen so that a saturated
194/// model has deviance zero.
195/// * aic: A version of Akaike's _An Information Criterion_, minus twice
196/// the maximized log-likelihood plus twice the number of
197/// parameters, computed via the ‘aic’ component of the family.
198/// For binomial and Poison families the dispersion is fixed at
199/// one and the number of parameters is the number of
200/// coefficients. For gaussian, Gamma and inverse gaussian
201/// families the dispersion is estimated from the residual
202/// deviance, and the number of parameters is the number of
203/// coefficients plus one. For a gaussian family the MLE of the
204/// dispersion is used so this is a valid value of AIC, but for
205/// Gamma and inverse gaussian families it is not. For families
206/// fitted by quasi-likelihood the value is ‘NA’.
207/// * null.deviance: The deviance for the null model, comparable with
208/// ‘deviance’. The null model will include the offset, and an
209/// intercept if there is one in the model. Note that this will
210/// be incorrect if the link function depends on the data other
211/// than through the fitted mean: specify a zero offset to force
212/// a correct calculation.
213/// * iter: the number of iterations of IWLS used.
214/// * weights: the _working_ weights, that is the weights in the final
215/// iteration of the IWLS fit.
216/// * prior.weights: the weights initially supplied, a vector of ‘1’s if none
217/// were.
218/// * df.residual: the residual degrees of freedom.
219/// * df.null: the residual degrees of freedom for the null model.
220/// * y: if requested (the default) the ‘y’ vector used. (It is a
221/// vector even for a binomial model.)
222/// * x: if requested, the model matrix.
223/// * model: if requested (the default), the model frame.
224/// * converged: logical. Was the IWLS algorithm judged to have converged?
225/// * boundary: logical. Is the fitted value on the boundary of the
226/// attainable values?
227/// * call: the matched call.
228/// * formula: the formula supplied.
229/// * terms: the ‘terms’ object used.
230/// * data: the ‘data argument’.
231/// * offset: the offset vector used.
232/// * control: the value of the ‘control’ argument used.
233/// * method: the name of the fitter function used (when provided as a
234/// ‘character’ string to ‘glm()’) or the fitter ‘function’ (when
235/// provided as that).
236/// * contrasts: (where relevant) the contrasts used.
237/// * xlevels: (where relevant) a record of the levels of the factors used
238/// in fitting.
239/// * na.action: (where relevant) information returned by ‘model.frame’ on
240/// the special handling of ‘NA’s.
241/// In addition, non-empty fits will have components ‘qr’, ‘R’ and
242/// ‘effects’ relating to the final weighted linear fit.
243///
244/// Objects of class ‘"glm"’ are normally of class ‘c("glm", "lm")’,
245/// that is inherit from class ‘"lm"’, and well-designed methods for
246/// class ‘"lm"’ will be applied to the weighted linear model at the
247/// final iteration of IWLS. However, care is needed, as extractor
248/// functions for class ‘"glm"’ such as ‘residuals’ and ‘weights’ do
249/// *not* just pick out the component of the fit with the same name.
250///
251/// If a ‘binomial’ ‘glm’ model was specified by giving a two-column
252/// response, the weights returned by ‘prior.weights’ are the total
253/// numbers of cases (factored by the supplied case weights) and the
254/// component ‘y’ of the result is the proportion of successes.
255///
256/// ## Fitting functions:
257///
258/// The argument ‘method’ serves two purposes. One is to allow the
259/// model frame to be recreated with no fitting. The other is to
260/// allow the default fitting function ‘glm.fit’ to be replaced by a
261/// function which takes the same arguments and uses a different
262/// fitting algorithm. If ‘glm.fit’ is supplied as a character string
263/// it is used to search for a function of that name, starting in the
264/// ‘stats’ namespace.
265///
266/// The class of the object return by the fitter (if any) will be
267/// prepended to the class returned by ‘glm’.
268///
269/// ## Author(s):
270///
271/// The original R implementation of ‘glm’ was written by Simon Davies
272/// working for Ross Ihaka at the University of Auckland, but has
273/// since been extensively re-written by members of the R Core team.
274///
275/// The design was inspired by the S function of the same name
276/// described in Hastie & Pregibon (1992).
277///
278/// ## References:
279///
280/// Dobson, A. J. (1990) _An Introduction to Generalized Linear
281/// Models._ London: Chapman and Hall.
282///
283/// Hastie, T. J. and Pregibon, D. (1992) _Generalized linear models._
284/// Chapter 6 of _Statistical Models in S_ eds J. M. Chambers and T.
285/// J. Hastie, Wadsworth & Brooks/Cole.
286///
287/// McCullagh P. and Nelder, J. A. (1989) _Generalized Linear Models._
288/// London: Chapman and Hall.
289///
290/// Venables, W. N. and Ripley, B. D. (2002) _Modern Applied
291/// Statistics with S._ New York: Springer.
292///
293/// ## See Also:
294///
295/// ‘anova.glm’, ‘summary.glm’, etc. for ‘glm’ methods, and the
296/// generic functions ‘anova’, ‘summary’, ‘effects’, ‘fitted.values’,
297/// and ‘residuals’.
298///
299/// ‘lm’ for non-generalized _linear_ models (which SAS calls GLMs,
300/// for ‘general’ linear models).
301///
302/// ‘loglin’ and ‘loglm’ (package ‘MASS’) for fitting log-linear
303/// models (which binomial and Poisson GLMs are) to contingency
304/// tables.
305///
306/// ‘bigglm’ in package ‘biglm’ for an alternative way to fit GLMs to
307/// large datasets (especially those with many cases).
308///
309/// ‘esoph’, ‘infert’ and ‘predict.glm’ have examples of fitting
310/// binomial glms.
311///
312/// ## Examples:
313///
314/// ```r
315/// ## Dobson (1990) Page 93: Randomized Controlled Trial :
316/// counts <- c(18,17,15,20,10,20,25,13,12)
317/// outcome <- gl(3,1,9)
318/// treatment <- gl(3,3)
319/// data.frame(treatment, outcome, counts) # showing data
320/// glm.D93 <- glm(counts ~ outcome + treatment, family = poisson())
321/// anova(glm.D93)
322/// summary(glm.D93)
323/// ## Computing AIC [in many ways]:
324/// (A0 <- AIC(glm.D93))
325/// (ll <- logLik(glm.D93))
326/// A1 <- -2*c(ll) + 2*attr(ll, "df")
327/// A2 <- glm.D93$family$aic(counts, mu=fitted(glm.D93), wt=1) +
328/// 2 * length(coef(glm.D93))
329/// stopifnot(exprs = {
330/// all.equal(A0, A1)
331/// all.equal(A1, A2)
332/// all.equal(A1, glm.D93$aic)
333/// })
334///
335///
336/// ## an example with offsets from Venables & Ripley (2002, p.189)
337/// utils::data(anorexia, package = "MASS")
338///
339/// anorex.1 <- glm(Postwt ~ Prewt + Treat + offset(Prewt),
340/// family = gaussian, data = anorexia)
341/// summary(anorex.1)
342///
343///
344/// # A Gamma example, from McCullagh & Nelder (1989, pp. 300-2)
345/// clotting <- data.frame(
346/// u = c(5,10,15,20,30,40,60,80,100),
347/// lot1 = c(118,58,42,35,27,25,21,19,18),
348/// lot2 = c(69,35,26,21,18,16,13,12,12))
349/// summary(glm(lot1 ~ log(u), data = clotting, family = Gamma))
350/// summary(glm(lot2 ~ log(u), data = clotting, family = Gamma))
351/// ## Aliased ("S"ingular) -> 1 NA coefficient
352/// (fS <- glm(lot2 ~ log(u) + log(u^2), data = clotting, family = Gamma))
353/// tools::assertError(update(fS, singular.ok=FALSE), verbose=interactive())
354/// ## -> .. "singular fit encountered"
355///
356/// ## Not run:
357///
358/// ## for an example of the use of a terms object as a formula
359/// demo(glm.vr)
360/// ## End(Not run)
361/// ```
362
363#[derive(Clone, Debug)]
364pub struct GeneralizedLinearRegression<L: Link, F: Family<L>> {
365 pub(crate) x: ModelMatrix,
366 pub(crate) y: ModelMatrix,
367 pub(crate) w: ModelMatrix,
368 pub(crate) alpha: Alpha64,
369 pub(crate) family: F,
370 pub(crate) _link: PhantomData<L>,
371 pub(crate) x1: ModelMatrix,
372 pub(crate) b: DMatrix<f64>,
373 pub(crate) model_data: (ModelMatrix, DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
374 pub(crate) eta: DMatrix<f64>,
375 pub(crate) mu: DMatrix<f64>,
376 pub(crate) aic: f64,
377 pub(crate) null_deviance: f64,
378 pub(crate) residual_deviance: f64,
379 pub(crate) final_weights: DMatrix<f64>,
380}
381
382impl<L: Link + Clone + 'static, F: Family<L> + Clone + 'static> Display
383 for GeneralizedLinearRegression<L, F>
384{
385 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
386 let mut s = self.clone();
387
388 // Write residuals table
389 writeln!(f, "Residuals:")?;
390 let headers = vec![
391 "Minimum".to_string(),
392 "1st Quantile".to_string(),
393 "Median".to_string(),
394 "3rd Quantile".to_string(),
395 "Maximum".to_string(),
396 ];
397 let lines = vec![s
398 .residuals()
399 .unwrap()
400 .matrix()
401 .as_slice()
402 .quantile(&[0.0, 0.25, 0.5, 0.75, 1.0], QuantileType::S)
403 .into_iter()
404 .map(|f| f)
405 .collect::<Vec<_>>()];
406 let row_names = Vec::new();
407 writeln!(f, "{}", DisplayTable::new(headers, row_names, lines, None))?;
408
409 // Write coefficients table
410 writeln!(f, "Coefficients:")?;
411 let headers = vec![
412 "Estimate".to_string(),
413 "Confidence Interval (L)".to_string(),
414 "Confidence Interval (U)".to_string(),
415 "T-Value".to_string(),
416 "P-Value".to_string(),
417 ];
418 let mut row_names = Vec::new();
419 let mut lines = Vec::new();
420 for coef in s.clone().test(&()).unwrap().significant_coef_tests {
421 row_names.push(coef.name.clone());
422 lines.push(vec![
423 coef.estimate(),
424 coef.confidence_interval().0,
425 coef.confidence_interval().1,
426 coef.statistic(),
427 coef.probability_value(),
428 ]);
429 }
430 writeln!(f, "{}", DisplayTable::new(headers, row_names, lines, None))?;
431
432 // Write tests table
433 writeln!(f, "Tests:")?;
434 let headers = vec![
435 "Statistic".to_string(),
436 "P-Value".to_string(),
437 "Alpha".to_string(),
438 ];
439 let mut row_names = Vec::new();
440 let mut lines = Vec::new();
441
442 let rsq = s.determination().unwrap();
443 row_names.push("Multiple R-squared (Robust)".to_string());
444 lines.push(vec![
445 rsq.statistic(),
446 rsq.probability_value(),
447 rsq.alpha().unwrap(),
448 ]);
449
450 let significance = s.test(&()).unwrap().significance_test;
451 row_names.push("Significance of Regression".to_string());
452 lines.push(vec![
453 significance.statistic(),
454 significance.probability_value(),
455 significance.alpha().unwrap(),
456 ]);
457
458 let resid = s.test(&()).unwrap().residual_test;
459 row_names.push("Shapiro-Wilk Normal Residual".to_string());
460 lines.push(vec![
461 resid.statistic(),
462 resid.probability_value(),
463 resid.alpha().unwrap(),
464 ]);
465
466 row_names.push("Null Deviance".to_string());
467 lines.push(vec![s.null_deviance, 0.0, 0.0]);
468
469 row_names.push("Residual Deviance".to_string());
470 lines.push(vec![s.residual_deviance, 0.0, 0.0]);
471
472 row_names.push("AIC".to_string());
473 lines.push(vec![s.aic, 0.0, 0.0]);
474
475 writeln!(f, "{}", DisplayTable::new(headers, row_names, lines, None))?;
476
477 Ok(())
478 }
479}