GLM.jl

repository·master·Indexed 20 days ago

https://github.com/juliastats/glm.jl

A Julia package for fitting linear and generalized linear models (GLM). It provides tools for regression analysis, including the `lm` function for Ordinary Least Squares and the `glm` function for models with various distributions (e.g., Poisson, Binomial) and link functions (e.g., LogitLink, ProbitLink). The library supports formula and matrix interfaces, categorical variable dummy coding, weighted estimation via StatsBase.jl, and specialized functions like `negbin` for negative binomial regression.

Tokens
5.8K
Snippets
23
Records
27
Agent score
71%

What's inside GLM.jl

  1. Choose between Cholesky and QR methods in `lm`

    master

    The lm function uses Cholesky factorization by default, which is fast but can be numerically unstable for ill-conditioned design matrices or aggressive in detecting multicollinearity.

    To improve numerical stability or handle multicollinearity, use the method keyword argument:

    • method=:cholesky (Default): Fast, but sensitive to ill-conditioned matrices and multicollinearity.
    • method=:qr: More stable for ill-conditioned design matrices and less aggressive in dropping collinear columns.

    If you encounter models where coefficients appear as NaN or parameters are not estimated due to multicollinearity detection, try switching to method=:qr.

    # Using QR decomposition for better stability
    ols = lm(@formula(Y ~ X), data; method=:qr)
    
    # Using Cholesky decomposition (default)
    ols = lm(@formula(Y ~ X), data; method=:cholesky)
  2. Handle Categorical Variables in GLM

    master

    GLM.jl automatically handles categorical variables via dummy coding. You can trigger this in two ways:

    1. Using CategoricalVectors: If a column in your table is a CategoricalVector (created via categorical() or categorical!()), it will be dummy coded by default.
    2. Using the contrasts argument: You can pass an explicit contrasts argument to the glm (or lm) function to specify a different coding system (e.g., DummyCoding()).

    Note: The response (dependent) variable must be numeric and cannot be categorical.

    using CategoricalArrays, DataFrames, GLM, StableRNGs
    
    # Method 1: Using CategoricalVectors
    rng = StableRNG(1)
    data = DataFrame(y = rand(rng, 100), x = categorical(repeat([1, 2, 3, 4], 25)))
    model = lm(@formula(y ~ x), data)
    
    # Method 2: Using explicit contrasts
    data_numeric = DataFrame(y = rand(StableRNG(1), 100), x = repeat([1, 2, 3, 4], 25))
    model = lm(@formula(y ~ x), data_numeric, contrasts = Dict(:x => DummyCoding()))
  3. How response and predictor objects are separated

    master

    GLM.jl uses a design pattern that separates functionality related to the response (ModResp) from functionality related to the linear predictor (LinPred). This allows the package to mix and match different statistical distributions with different types of model matrices.

    LinPred (Linear Predictor)

    • Incorporates the parameter vector and the model matrix.
    • The parameter vector is a dense numeric vector; the model matrix can be dense or sparse.
    • Must implement a decomposition of the weighted model matrix to solve the system X'W * X * delta = X'wres.
    • Common dense types include DensePredQR (more accurate) and DensePredChol (faster).

    ModResp (Model Response)

    • Provides methods for wtres and sqrtxwts generics.
    • These values are used by LinPred types during the fitting process (via updatebeta).
    • The updatedelta method returns the convergence criterion.
    • The updatemu method (which takes the linpred result) returns the updated deviance.
  4. Apply weights to `lm` and `glm` models

    master

    Both lm and glm support weighted estimation. To use weights, you must wrap your weight vector in one of the specific weight types defined in StatsBase.jl and pass it to the weights keyword argument.

    Important: Passing a raw AbstractVector as weights is deprecated. It will be coerced to FrequencyWeights and trigger a warning. Always use the explicit weight constructors.

    Weight Types:

    • AnalyticWeights (via aweights): Used for non-random relative importance (e.g., reliability or precision weights). Often used for aggregate values with differing variances.
    • FrequencyWeights (via fweights): Used when weights represent the number of times an observation was seen (case weights).
    • ProbabilityWeights (via pweights): Used to correct for sampling probabilities (sampling weights).
    • UnitWeights: The default (unweighted) behavior.

    Note that the weight type affects the variance of estimated coefficients and related quantities (like standard errors and log-likelihood), but the coefficient point estimates themselves remain the same regardless of the weight type used.

    using StableRNGs, DataFrames, StatsBase, GLM
    
    data = DataFrame(y = rand(StableRNG(1), 100), x = randn(StableRNG(2), 100), weights = repeat([1, 2, 3, 4], 25));
    
    # Using Analytic Weights
    m_aweights = lm(@formula(y ~ x), data, weights=aweights(data.weights))
    
    # Using Frequency Weights
    m_fweights = lm(@formula(y ~ x), data, weights=fweights(data.weights))
    
    # Using Probability Weights
    m_pweights = lm(@formula(y ~ x), data, weights=pweights(data.weights))
  5. Debug failed GLM fits

    master

    If a generalized linear model fit fails, you can enable detailed iteration output by setting the JULIA_DEBUG environment variable to GLM. This will output the deviance and the change in deviance for each iteration of the fitting process.

    # Run this in your terminal before starting Julia, or set it within your environment
    ENV["JULIA_DEBUG"] = "GLM"
  6. Perform Negative binomial regression

    master

    Negative binomial regression can be performed in two ways using the GLM.jl package:

    1. Using glm: Pass NegativeBinomial(theta) as the family argument to glm, where theta is the dispersion parameter.
    2. Using negbin: A specialized convenience function negbin that estimates the dispersion parameter automatically.

    Both methods require a link function, such as LogLink().

    using GLM, RDatasets
    
    quine = dataset("MASS", "quine")
    
    # Method 1: Using glm with a specified theta
    nbrmodel = glm(@formula(Days ~ Eth+Sex+Age+Lrn), quine, NegativeBinomial(2.0), LogLink())
    
    # Method 2: Using the negbin convenience function (estimates theta)
    nbrmodel = negbin(@formula(Days ~ Eth+Sex+Age+Lrn), quine, LogLink())
    
    # Accessing the estimated theta from the model
    println("Estimated theta = ", round(nbrmodel.rr.d.r, digits=5))
  7. Perform Probit regression

    master

    To perform a Probit regression, use the glm function with a Binomial() family and a ProbitLink() link function. This is typically used when the response variable is binary (0 or 1).

    using GLM, DataFrames
    
    data = DataFrame(X=[1,2,2], Y=[1,0,1])
    probit = glm(@formula(Y ~ X), data, Binomial(), ProbitLink())
  8. Use column names for weights in DataFrames

    master

    For convenience, you can store pre-constructed weight objects directly in a DataFrame column. You can then pass the column name as a symbol to the weights keyword argument in lm or glm.

    # Pre-construct the weights in the DataFrame
    data.weights = aweights(data.weights);
    
    # Pass the column name as a symbol
    m_aweights = lm(@formula(y ~ x), data, weights=:weights)
  9. Perform linear regression with PowerLink

    master

    You can use PowerLink(λ) as a link function in a glm call to model data where the relationship between the mean and the linear predictor follows a power law. To find the optimal power parameter λ, you can wrap the bic_glm calculation (using the bic function) in an optimization routine like Optim.optimize to minimize the Bayesian Information Criterion (BIC).

    using GLM, RDatasets, StatsBase, DataFrames, Optim
    
    # Load dataset
    trees = DataFrame(dataset("datasets", "trees"));
    
    # Define a function to calculate BIC for a given λ
    bic_glm(λ) = bic(glm(@formula(Volume ~ Height + Girth), trees, Normal(), PowerLink(λ)));
    
    # Optimize λ to minimize BIC
    optimal_bic = optimize(bic_glm, -1.0, 1.0);
    
    # Use the optimal λ to fit the best model
    optimal_λ = round(optimal_bic.minimizer, digits = 5)
    model = glm(@formula(Volume ~ Height + Girth), trees, Normal(), PowerLink(optimal_λ))
  10. Extract statistics and predictions from a model

    master

    Once a model is fitted, you can use several methods to inspect the results, perform predictions, or check model fit:

    • StatsBase.predict(model, X): Predict response values for new data X.
    • StatsBase.nobs(model): Returns the number of observations.
    • StatsBase.deviance(model): Returns the deviance.
    • StatsBase.nulldeviance(model): Returns the null deviance.
    • GLM.ftest(model): Performs an F-test.
    • GLM.dispersion(model): Returns the dispersion parameter.
    • cooksdistance(model): Calculates Cook's distance for each observation.