Skip to main content

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}