R/methods.R

Defines functions ltm logsum chol2vcov print.summary.vcov.mlogit summary.vcov.mlogit print.vcov.mlogit vcov.mlogit effects.mlogit coef.summary.mlogit coef.mlogit fitted.mlogit predict.mlogit idx_name.mlogit idx.mlogit print.summary.mlogit logLik.mlogit model.response.mlogit model.matrix.mlogit terms.mlogit df.residual.mlogit residuals.mlogit

Documented in coef.mlogit coef.summary.mlogit df.residual.mlogit effects.mlogit fitted.mlogit idx.mlogit idx_name.mlogit logLik.mlogit logsum model.matrix.mlogit model.response.mlogit predict.mlogit print.summary.mlogit print.summary.vcov.mlogit print.vcov.mlogit residuals.mlogit summary.vcov.mlogit terms.mlogit vcov.mlogit

#' Methods for mlogit objects
#'
#' Miscellaneous methods for `mlogit` objects.
#' 
#' @name miscmethods.mlogit
#' @aliases residuals.mlogit df.residual.mlogit terms.mlogit
#'     model.matrix.mlogit model.response.mlogit update.mlogit
#'     print.mlogit logLik.mlogit summary.mlogit print.summary.mlogit
#'     predict.mlogit fitted.mlogit coef.mlogit
#'     coef.summary.mlogit
#' @param x,object an object of class `mlogit`
#' @param subset an optional vector of coefficients to extract for the
#'     `coef` method,
#' @param digits the number of digits,
#' @param width the width of the printing,
#' @param new an updated formula for the `update` method,
#' @param newdata a `data.frame` for the `predict` method,
#' @param outcome a boolean which indicates, for the `fitted` and the
#'     `residuals` methods whether a matrix (for each choice, one
#'     value for each alternative) or a vector (for each choice, only
#'     a value for the alternative chosen) should be returned,
#' @param type one of `outcome` (probability of the chosen
#'     alternative), `probabilities` (probabilities for all the
#'     alternatives), `parameters` for individual-level random
#'     parameters for the fitted method, how the correlated random
#'     parameters should be displayed : `"chol"` for the estimated
#'     parameters (the elements of the Cholesky decomposition matrix),
#'     `"cov"` for the covariance matrix and `"cor"` for the
#'     correlation matrix and the standard deviations,
#' @param returnData for the `predict` method, if `TRUE`, the data is
#'     returned as an attribute,
#' @param fixed if `FALSE` (the default), constant coefficients are
#'     not returned,
#' @param n,m see [dfidx::idx()]
#' @param ... further arguments.
#' 
#' @rdname miscmethods.mlogit
#' @export
residuals.mlogit <- function(object, outcome = TRUE, ...){
    if (! outcome){
        result <- object$residuals
    }
    else{
        J <- ncol(object$residuals)
        y <- matrix(model.response(object$model), ncol = J, byrow = T)
        result <- apply(y * object$residuals, 1, sum)
    }
    result
}

#' @rdname miscmethods.mlogit
#' @export
df.residual.mlogit <- function(object, ...){
    n <- length(residuals(object))
    K <- length(coef(object))
    n - K
}

#' @rdname miscmethods.mlogit
#' @export
terms.mlogit <- function(x, ...){
    terms(x$formula)
}

#' @rdname miscmethods.mlogit
#' @export
model.matrix.mlogit <- function(object, ...){
    model.matrix(object$model)
}

#' @rdname miscmethods.mlogit
#' @export
model.response.mlogit <- function(object, ...){
    y.name <- paste(deparse(object$formula[[2]]))
    object$model[[y.name]]
}

#' @rdname miscmethods.mlogit
#' @export
update.mlogit <- function (object, new, ...){
    call <- object$call
    if (is.null(call))
        stop("need an object with call component")
    extras <- match.call(expand.dots = FALSE)$...
    if (! missing(new))
        call$formula <- update(formula(object), new)
    if(length(extras) > 0) {
        existing <- ! is.na(match(names(extras), names(call)))
        ## do these individually to allow NULL to remove entries.
        for (a in names(extras)[existing]) call[[a]] <- extras[[a]]
        if(any(!existing)) {
            call <- c(as.list(call), extras[!existing])
            call <- as.call(call)
        }
    }
    eval(call, parent.frame())
}

#' @rdname miscmethods.mlogit
#' @export
print.mlogit <- function (x, digits = max(3, getOption("digits") - 2),
                          width = getOption("width"), ...){
    cat("\nCall:\n", deparse(x$call), "\n\n", sep = "")
    if (length(coef(x))) {
        cat("Coefficients:\n")
        print.default(format(coef(x), digits = digits), print.gap = 2, 
                      quote = FALSE)
    }
    else cat("No coefficients\n")
    cat("\n")
    invisible(x)
}

#' @rdname miscmethods.mlogit
#' @export
logLik.mlogit <- function(object,...){
    object$logLik
}

#' @rdname miscmethods.mlogit
#' @export
summary.mlogit <- function (object, ..., type = c("chol", "cov", "cor")){
    type <- match.arg(type)
    fixed <- attr(object$coefficients, "fixed")
    #    b <- coef(object)[! fixed]
    b <- coef(object)
    std.err <- sqrt(diag(vcov(object)))
    z <- b / std.err
    p <- 2 * (1 - pnorm(abs(z)))
    CoefTable <- cbind(b, std.err, z, p)
    colnames(CoefTable) <- c("Estimate", "Std. Error", "z-value", "Pr(>|z|)")
    if (type != "chol"){
        sumvcov <- summary(vcov(object, what = "rpar", type = type))
        CoefTable[grep("chol.", rownames(CoefTable)), ] <- sumvcov
        rownames(CoefTable)[grep("chol.", rownames(CoefTable))] <- rownames(sumvcov)
    }
    object$CoefTable <- CoefTable
    if (has.intercept(object)){
        object$lratio <- lratio(object)
        object$mfR2 <- mfR2(object)
    }
    if (! is.null(object$rpar)){
        rpar <- object$rpar
        object$summary.rpar <- t(sapply(rpar, summary))
    }
    class(object) <- c("summary.mlogit", "mlogit")
    return(object)
}

#' @rdname miscmethods.mlogit
#' @method print summary.mlogit
#' @export
print.summary.mlogit <- function(x, digits = max(3, getOption("digits") - 2),
                                 width = getOption("width"), ...){
    cat("\nCall:\n")
    print(x$call)
    cat("\n")
    cat("Frequencies of alternatives:")
    print(prop.table(x$freq), digits = digits)
    cat("\n")
    print(x$est.stat)
    cat("\nCoefficients :\n")
    printCoefmat(x$CoefTable, digits = digits)
    cat("\n")
    cat(paste("Log-Likelihood: ", signif(x$logLik, digits), "\n", sep = ""))
    if (has.intercept(x)){
        cat("McFadden R^2: ", signif(x$mfR2, digits), "\n")
        cat("Likelihood ratio test : ", names(x$lratio$statistic),
            " = ", signif(x$lratio$statistic, digits),
            " (p.value = ", format.pval(x$lratio$p.value, digits = digits), ")\n", sep = "")
    }
    if ( !is.null(x$summary.rpar)){
        cat("\nrandom coefficients\n")
        print(x$summary.rpar)
    }
    invisible(x)
}

#' @rdname miscmethods.mlogit
#' @export
idx.mlogit <- function(x, n = NULL, m = NULL){
    dfidx::idx(model.frame(x), n = n, m = m)
}

#' @rdname miscmethods.mlogit
#' @export
idx_name.mlogit <- function(x, n = NULL, m = NULL){
    idx_name(model.frame(x), n = n, m = m)
}

#' @rdname miscmethods.mlogit
#' @export
predict.mlogit <- function(object, newdata = NULL, returnData = FALSE, ...){
    # if no newdata is provided, use the mean of the model.frame
    if (is.null(newdata)){
        newdata <- mean(object$model)
        #YC2020/03/05 coerce it to a data.frame so that it gets transformed 
        newdata <- as.data.frame(newdata)
    }
    # if newdata is not a mlogit.data, it is coerced below
    if ((! inherits(newdata, "mlogit.data")) & (! inherits(newdata, "dfidx"))){
        if (FALSE){
            rownames(newdata) <- NULL
            lev <- colnames(object$probabilities)
            J <- length(lev)
            choice.name <- attr(model.frame(object), "choice")
            if (nrow(newdata) %% J)
                stop("the number of rows of the data.frame should be a multiple of the number of alternatives")
            attr(newdata, "index") <- data.frame(chid = rep(1:(nrow(newdata) %/% J ), each = J), alt = rep(lev, J))
            attr(newdata, "class") <- c("mlogit.data", "data.frame")
            if (is.null(newdata[['choice.name']])){
                newdata[[choice.name]] <- FALSE
                newdata[[choice.name]][1] <- TRUE # probit and hev requires that one (arbitrary) choice is TRUE
            }
        }
        else{
            # New stuff, coerce manually to an dfidx object
            lev <- colnames(object$probabilities)
            J <- length(lev)
            choice.name <- paste(deparse(formula(object)[[2]]))
            if (nrow(newdata) %% J)
                stop("the number of rows of the data.frame should be a multiple of the number of alternatives")
            nbid <- nrow(newdata) %/% J
            newdata$idx <- data.frame(chid = factor(rep(1:nbid, each = J)), alt = factor(rep(lev, nbid), levels = lev))
            class(newdata$idx) <- c("idx", "data.frame")
            attr(newdata$idx, "ids") <- c(1, 2)
            attr(newdata, "clseries") <- c("xseries_mlogit", "xseries")
            attr(newdata, "class") <- c("dfidx_mlogit", "dfidx", "data.frame")
            #YC2020/03/05 this seems incorect, replace 'choice.name' by choice.name
   #        if (is.null(newdata[['choice.name']])){
            if (is.null(newdata[[choice.name]])){
                newdata[[choice.name]] <- FALSE
                newdata[[choice.name]][1] <- TRUE # probit and hev requires that one (arbitrary) choice is TRUE
            }
        }
    }
    # if the updated model requires the use of mlogit.data, suppress all
    # the relevant arguments
    m <- match(c("choice", "shape", "varying", "sep",
                 "alt.var", "chid.var", "alt.levels",
                 "opposite", "drop.index", "id", "ranked"),
               names(object$call), 0L)
    if (sum(m) > 0) object$call <- object$call[ - m]
    # update the model and get the probabilities
    newobject <- update(object, start = coef(object, fixed = TRUE), data = newdata, iterlim = 0, print.level = 0)
#    newobject <- update(object, start = coef(object), data = newdata, iterlim = 0, print.level = 0)
    result <- newobject$probabilities
    if (nrow(result) == 1){
        result <- as.numeric(result)
        names(result) <- colnames(object$probabilities)
    }
    if (returnData) attr(result, "data") <- newdata
    result
}

#' @rdname miscmethods.mlogit
#' @export
fitted.mlogit <- function(object, type = c("outcome", "probabilities",
                                           "linpred", "parameters"),
                          outcome = NULL, ...){
    if (! is.null(outcome)){
        if (outcome) result <- object$fitted
        else result <- object$probabilities
    }
    else{
        type <- match.arg(type)
        result <- switch(type,
                        outcome = object$fitted,
                        probabilities = object$probabilities,
                        linpred = object$linpred,
                        parameters = object$indpar)
    }
    result
}

#' @rdname miscmethods.mlogit
#' @export
coef.mlogit <- function(object,
                        subset = c("all", "iv", "sig", "sd", "sp", "chol"),
                        fixed = FALSE, ...){
    whichcoef <- match.arg(subset)
    result <- object$coefficients
    ncoefs <- names(result)
    # first remove the fixed coefficients if required
    if (! fixed) result <- result[! attr(result, "fixed")]
    attr(result, "fixed") <- NULL
    if (whichcoef == "all") selcoef <- 1:length(result)
    else selcoef <- grep(whichcoef, ncoefs)
    result[selcoef]
}
    
#' @rdname miscmethods.mlogit
#' @method coef summary.mlogit
#' @export
coef.summary.mlogit <- function(object, ...){
    result <- object$CoefTable
    result
}

#' Marginal effects of the covariates
#' 
#' The `effects` method for `mlogit` objects computes the marginal
#' effects of the selected covariate on the probabilities of choosing the
#' alternatives
#' 
#' @name effects.mlogit
#' @param object a `mlogit` object,
#' @param covariate the name of the covariate for which the effect should be
#' computed,
#' @param type the effect is a ratio of two marginal variations of the
#' probability and of the covariate ; these variations can be absolute
#' `"a"` or relative `"r"`. This argument is a string that contains
#' two letters, the first refers to the probability, the second to the
#' covariate,
#' @param data a data.frame containing the values for which the effects should
#' be calculated. The number of lines of this data.frame should be equal to the
#' number of alternatives,
#' @param ... further arguments.
#' @return If the covariate is alternative specific, a \eqn{J \times J} matrix is
#' returned, \eqn{J} being the number of alternatives. Each line contains the
#' marginal effects of the covariate of one alternative on the probability to
#' choose any alternative. If the covariate is individual specific, a vector of
#' length \eqn{J} is returned.
#' @export
#' @author Yves Croissant
#' @seealso  [mlogit()] for the estimation of multinomial logit
#' models.
#' @keywords regression
#' @examples
#' 
#' data("Fishing", package = "mlogit")
#' library("zoo")
#' Fish <- mlogit.data(Fishing, varying = c(2:9), shape = "wide", choice = "mode")
#' m <- mlogit(mode ~ price | income | catch, data = Fish)
#' # compute a data.frame containing the mean value of the covariates in
#' # the sample
#' z <- with(Fish, data.frame(price = tapply(price, idx(m, 2), mean),
#'                            catch = tapply(catch, idx(m, 2), mean),
#'                            income = mean(income)))
#' # compute the marginal effects (the second one is an elasticity
#' ## IGNORE_RDIFF_BEGIN
#' effects(m, covariate = "income", data = z)
#' ## IGNORE_RDIFF_END
#' effects(m, covariate = "price", type = "rr", data = z)
#' effects(m, covariate = "catch", type = "ar", data = z)
effects.mlogit <- function(object, covariate = NULL,
                           type = c("aa", "ar", "rr", "ra"),
                           data = NULL, ...){
    type <- match.arg(type)
    if (is.null(data)){
        P <- predict(object, returnData = TRUE)
        data <- attr(P, "data")
        attr(P, "data") <- NULL
    }
    else P <- predict(object, data)
    newdata <- data
    J <- length(P)
    alt.levels <- names(P)
    pVar <- substr(type, 1, 1)
    xVar <- substr(type, 2, 2)
#    cov.list <- lapply(attr(formula(object), "rhs"), as.character)
    nrhs <- length(formula(object))[2]
    cov.list <- vector(length = 3, mode = "list")
    for (i in 1:nrhs) cov.list[[i]] <-
                          attr(terms(formula(object), rhs = i), "term.labels")
    rhs <- sapply(cov.list, function(x) length(na.omit(match(x, covariate))) > 0)
    rhs <- (1:length(cov.list))[rhs]
    eps <- 1E-5
    if (rhs %in% c(1, 3)){
        if (rhs == 3){
            theCoef <- paste(alt.levels, covariate, sep = ":")
            theCoef <- coef(object)[theCoef]
        }
        else theCoef <- coef(object)[covariate]
        me <- c()
        for (l in 1:J){
            newdata[l, covariate] <- data[l, covariate] + eps
            newP <- predict(object, newdata)
            me <- rbind(me, (newP - P) / eps)
            newdata <- data
        }
        if (pVar == "r") me <- t(t(me) / P)
        if (xVar == "r") me <- me * matrix(rep(data[[covariate]], J), J)
        dimnames(me) <- list(alt.levels, alt.levels)
    }
    if (rhs == 2){
        newdata[, covariate] <- data[, covariate] + eps
        newP <- predict(object, newdata)
        me <- (newP - P) / eps
        if (pVar == "r") me <- me / P
        if (xVar == "r") me <- me * data[[covariate]]
        names(me) <- alt.levels
    }
    if (inherits(me, "xseries")) attr(me, "idx") <- NULL
    unclass(me)
}

#' vcov method for mlogit objects
#' 
#' The `vcov` method for `mlogit` objects extract the covariance
#' matrix of the coefficients, the errors or the random parameters.
#' 
#' This new interface replaces the `cor.mlogit` and `cov.mlogit`
#' functions which are deprecated.
#'
#' @name vcov.mlogit
#' 
#' @aliases vcov.mlogit print.vcov.mlogit summary.vcov.mlogit
#' print.summary.vcov.mlogit
#' @param object a `mlogit` object (and a `vcov.mlogit` for the
#' summary method),
#' @param x a `vcov.mlogit` or a `summary.vcov.mlogit` object,
#' @param what indicates which covariance matrix has to be extracted : the
#' default value is `coefficients`, in this case, `vcov` behaves as
#' usual. If `what` equals `errors` the covariance matrix of the
#' errors of the model is returned. Finally, if `what` equals `rpar`,
#' the covariance matrix of the random parameters are extracted,
#' @param subset the subset of the coefficients that have to be extracted (only
#' relevant if `what` ` = "coefficients"`),
#' @param type with this argument, the covariance matrix may be returned (the
#' default) ; the correlation matrix with the standard deviation on the
#' diagonal may also be extracted,
#' @param reflevel relevent for the extraction of the errors of a multinomial
#' probit model ; in this case the covariance matrix is of error differences is
#' returned and, with this argument, the alternative used for differentiation
#' is indicated,
#' @param digits the number of digits,
#' @param width the width of the printing,
#' @param ... further arguments.
#' @export
#' @author Yves Croissant
#' @seealso  [mlogit()] for the estimation of multinomial logit
#' models.
#' @keywords regression
vcov.mlogit <- function(object,
                        what = c('coefficient', 'errors', 'rpar'),
                        subset = c("all", "iv", "sig", "sd", "sp", "chol"),
                        type = c('cov', 'cor', 'sd'),
                        reflevel = NULL, ...){
    whichcoef <- match.arg(subset)
    what <- match.arg(what)
    type <- match.arg(type)
    fixed <- attr(object$coefficients, "fixed")
    ncoefs <- names(object$coefficients)

    # for the coefficients, we have to check the problem for fixed
    # coefficients
    if (what == 'coefficient'){
        if (whichcoef == "all") selcoef <- 1:length(ncoefs)
        else selcoef <- grep(whichcoef, ncoefs)
        if (any(fixed)) selcoef <- selcoef[! fixed]
        result <- solve(- object$hessian[selcoef, selcoef])
    }
    
    if (what == 'errors'){
        if (! is.null(object$omega)){
            if (is.null(reflevel)){
                if (is.list(object$omega)) result <- object$omega[[1]]
                else result <- object$omega
            }
            else result <- object$omega[[reflevel]]
        }
        result <- switch(type,
                         cov = result,
                         cor = result / tcrossprod(sqrt(diag(result))),
                         sd = sqrt(diag(result))
                         )
    }
    if (what == 'rpar'){
        if (is.null(object$rpar)) stop('no random parameters')
        nrpar <- names(object$rpar)
        if (is.null(attr(object$rpar, "covariance"))){
            # No correlated parameters
            result <- stdev(object)
            if (type != 'sd'){
                V  <- matrix(0, length(result), length(result),
                             dimnames = list(names(result), names(result)))
                if (type == 'cor') diag(V) <- 1
#                if (type == 'vcov') diag(V) <- result ^ 2
#                if (type == 'cov') V <- result ^ 2
                if (type == 'cov') diag(V) <- result ^ 2
                result <- V
            }
        }
        else{
            # correlated parameters
            coefs <- coef(object, subset = "chol")
            ncoefs <- names(coefs)
            # compute the vcov matrix of random parameters
            result <- tcrossprod(ltm(coefs, to = "ltm"))
            # compute the variance of the vcov matrix of random
            # parameters
            vcovstruct <- chol2vcov(object, type = type)
            if (type == "cov") attr(result, "cov") <- vcovstruct
            if (type == 'cor'){
                sd <- sqrt(diag(result))
                result <- result / tcrossprod(sqrt(diag(result)))
                diag(result) <- sd
                attr(result, "cov") <- vcovstruct
            }
            attr(result, "type") <- type
            ## if (type == 'cov'){
            ##     result <- diag(result)
            ##     attr(result, "cov") <- diag(ltm(vcovstruct, to = "ltm"))
            ## }
            if (type == 'sd') result <- sqrt(diag(result))
            nrparcor <- rownames(attr(object$rpar, "covariance"))
            if (is.null(dim(result))) names(result) <- nrparcor
            else dimnames(result) <- list(nrparcor, nrparcor)
        }
    }
    structure(result, class = c('vcov.mlogit', class(result)), type = type)
}

#' @rdname vcov.mlogit
#' @method print vcov.mlogit
#' @export
print.vcov.mlogit <- function(x, ...){
    attr(x, "cov") <- attr(x, "type") <- NULL
    print(unclass(x))
}

#' @rdname vcov.mlogit
#' @method summary vcov.mlogit
#' @export
summary.vcov.mlogit <- function(object, ...){
    if (is.null(attr(object, "cov")))
        stop("summary.vcov.mlogit only implemented for random parameters")
    if (is.matrix(object)){
        coefs <- ltm(object, to = "vec")
        nrpar <- rownames(object)
        K <- length(nrpar)
        type <- attr(object, "type")
        diag <- ifelse(type == "cov", "var", "sd")
        nstruct <- names.rpar(nrpar, prefix = type, diag = diag, unique = TRUE)
    }
    else{
        coefs <- object
        nstruct <- names(coefs)
    }
    std.err <- sqrt(attr(object, "cov"))
    b <- coefs
    z <- b / std.err
    p <- 2 * pnorm(abs(z), lower.tail = FALSE)
    # construct the object of class summary.plm
    coefficients <- cbind("Estimate"   = b,
                          "Std. Error" = std.err,
                          "z-value"    = z,
                          "Pr(>|z|)"   = p)
    rownames(coefficients) <- nstruct
    if (is.matrix(object)){
        diagpos <- (1:K) * ( (1:K) + 1) / 2
        coefficients <- coefficients[c(diagpos, (1:nrow(coefficients))[- diagpos]), ]
    }
    structure(coefficients, class = "summary.vcov.mlogit")
    }   

#' @rdname vcov.mlogit
#' @method print summary.vcov.mlogit
#' @export
print.summary.vcov.mlogit <- function(x, digits = max(3, getOption("digits") - 2),
                                      width = getOption("width"), ...){
    printCoefmat(x, digits = digits)
}

chol2vcov <- function(x, type = c("cov", "cor")){
    type <- match.arg(type)
    # Take a mlogit object as argument and returns a vector of
    # variance for the structural parameters
    # First get the position of the coefficients of the Cholesky
    # decomposition
    cholspos <- grep("chol.", names(coef(x)))
    # Then get these coefficients
    coefs <- coef(x)[cholspos]
    # and the covariance matrix of these coefficients
    vcovs <- vcov(x)[cholspos, cholspos]
    # compute the matrix of derivatives
    Dcholcov <- function(x){
        # x is a Cholesky matrix (lower triangular + diagonal),
        # entered as a matrix or as a vector ; Dchol returns the
        # matrix of derivatives of the structural parameters (variance
        # and covariance) respective to the estimated parameters (the
        # element of the Cholesky decomposition).
        if (! is.matrix(x)) x <- ltm(x, to = "ltm")
        K <- nrow(x)    
        Delta <- matrix(0, nrow = K * (K + 1) / 2, ncol = K * (K + 1) / 2)
        dims <- c(0, (1:K) * (1:K + 1) / 2)
        Delta[1, 1] <- x[1, 1]
        if (K > 1){
            for (i in 2:K){
                pos <- (dims[i] + 1):dims[i + 1]
                betas <- ltm(ltm(x, to = "vec")[1:dims[i + 1]], to = "ltm")
                Delta[pos, pos] <- betas
                for (j in 1:(i - 1)){
                    Delta[dims[i] + j, (dims[j] + 1):dims[j + 1]] <- x[i, 1:j]
                }
            }
        }
        dblrows <- (1:K) * ( (1:K) + 1) / 2
        Delta[dblrows, ] <- Delta[dblrows, ] * 2
        Delta
    }
    Dcovcor <- function(x){
        y <- ltm(x, to = "ltm")
        sd <- sqrt(diag(y))
        y <- y / outer(sd, sd)
        diag(y) <- sd
        x <- ltm(x, to = "vec")
        y <- ltm(y, to = "vec")
        dims <- length(x)
        K <- - 0.5 + sqrt(1 + 8 * dims) / 2
        diags <- (1:K) * ((1:K) + 1) / 2
        rows <- Reduce("c", lapply(1:K, function(x) 1:x))
        cols <- rep(1:K, 1:K)
        M <- matrix(0, dims, dims)
        for (i in 1:dims){
            if (cols[i] == rows[i]) M[i, i] <- 1 / 2 * y[i] / x[i]
            else{
                M[i, i] <- 1  * y[i] / x[i]
                first <- rows[i]
                second <- cols[i]      
                M[i, rows == first  & cols == first ] <- - 1 / 2  * y[i] / x[i]
                M[i, rows == second & cols == second] <- - 1 / 2  * y[i] / x[i]
            }   
        }
        M
    }
    Derchols <- Dcholcov(ltm(coefs, to = "ltm"))
    # estimate the covariance matrix of the structural parameters
    result <- Derchols %*% vcovs %*% t(Derchols)
    if (type == "cor"){
        coefs <- tcrossprod(ltm(coefs, to = "ltm"))
        Dercov <- Dcovcor(ltm(coefs, to = "ltm"))
        result <- Dercov %*% result %*% t(Dercov)
    }
    # coerce it to a vector and set the relevant names
    result <- diag(result)
    names(result) <- names(coef(x))[cholspos]
    result
}

#' Compute the log-sum or inclusive value/utility
#' 
#' The `logsum` function computes the inclusive value, or inclusive
#' utility, which is used to compute the surplus and to estimate the two steps
#' nested logit model.
#' 
#' @name logsum
#' @param coef a numerical vector or a `mlogit` object, from which the
#' `coef` vector is extracted,
#' @param X a matrix or a `mlogit` object from which the
#' `model.matrix` is extracted,
#' @param formula a formula or a `mlogit` object from which the
#' `formula` is extracted,
#' @param data a `data.frame` or a `mlogit` object from which the
#' `model.frame` is extracted,
#' @param type either `"group"` or `"global"` : if a `group`
#' argument has been provided in the `mlogit.data`, the inclusive values
#' are by default computed for every group, otherwise, a unique global
#' inclusive value is computed for each choice situation,
#' @param output the shape of the results: if `"chid"`, the results is a
#' vector (if `type = "global"`) or a matrix (if `type = "region"`)
#' with row number equal to the number of choice situation, if `"obs"` a
#' vector of length equal to the number of lines of the data in long format is
#' returned.
#' @details
#' The inclusive value, or inclusive utility, or log-sum is the log of the
#' denominator of the probabilities of the multinomial logit model. If a
#' `"group"` variable is provided in the `"mlogit.data"` function,
#' the denominator can either be the one of the multinomial model or those of
#' the lower model of the nested logit model.
#' 
#' If only one argument (`coef`) is provided, it should a `mlogit`
#' object and in this case, the `coefficients` and the `model.matrix`
#' are extracted from this model.
#' 
#' In order to provide a different `model.matrix`, further arguments could
#' be used. `X` is a `matrix` or a `mlogit` from which the
#' `model.matrix` is extracted. The `formula`-`data` interface
#' can also be used to construct the relevant `model.matrix`.
#' @return either a vector or a matrix.
#' @export
#' @author Yves Croissant
#' @seealso  [mlogit()] for the estimation of a multinomial logit
#' model.
#' @keywords regression
logsum <- function(coef, X = NULL, formula = NULL, data = NULL,
                   type = NULL, output = c("chid", "obs")){
    # the model.matrix is from model x
    # the coef is from model y

    # extract the coefs
    if (is.numeric(coef)) beta <- coef
    else{
        if (inherits(coef, "mlogit")) beta <- coef(coef)
        else stop("coef should be either a numeric or a mlogit object")
    }
    
    # extract the model.matrix

    # X, formula and data is NULL, in this case, extract the
    # model.matrix from the coef object
    if (is.null(X) & (is.null(data))){
        if (inherits(coef, "mlogit")){
            .idx <- dfidx::idx(coef)
            X <- model.matrix(coef)
        }
        else stop("only one argument is provided, it should be a mlogit object")           
    }
    else{
        # X is provided, in this case, the index is (by priority) the
        # index attribute of X if it exists, otherwise, it is coef's
        # and conformity should be checked
        if (! is.null(X)){
            if (! inherits(X, "matrix") & ! inherits(X, "mlogit"))
                stop("X should be either a matrix or a mlogit object")
            if (is.matrix(X)){
                if (! is.null(attr(X, "index"))) idx <- attr(X, "index")
                else{
                    if (inherits(coef, "mlogit")){
                        .idx <- dfidx::idx(coef)
                        if (nrow(.idx) != nrow(X)) stop("X has no index and its dimension is uncorrect")
                    }
                    else stop("no index in for the coef and the X argument")
                }
            }
            if (inherits(X, "mlogit")){
                .idx <- dfidx::idx(X)
                X <- model.matrix(X)
            }
        }
        else{
            if (is.null(data)) stop("the X or data argument should be provided")
            else{
                # data is provided, if it is a mlogit object, extract
                # the model.frame
                if (inherits(data, "mlogit")) data <- model.frame(data)
                # if it is an ordinary data.frame, coerce it using the
                # index extracted from the coef argument
                if (! is.data.frame(data)) stop("data should be a data.frame")
                if ((! inherits(data, "mlogit.data")) & (! inherits(data, "dfidx"))){
                    if (is.null(attr(coef, "index")))
                        stop("no index available to compute the model.matrix")
                    else{
                        .idx <- dfidx::idx(coef)
                        if (nrow(.idx) != nrow(data)) stop("uncompatible dimensions")
                        else{
                            data <- structure(data, index = .idx,
                                              class = c("mlogit.data", "data.frame"))
                        }
                    }
                }
                else .idx <- dfidx::idx(data)

                if (! is.null(formula)) X <- model.matrix(formula, data)
                else{
                    if (inherits(coef, "mlogit")){
                        mf <- update(coef, data = data, estimate = FALSE)
                        .idx <- dfidx::idx(mf)
                        X <- model.matrix(mf)
                    }
                    else stop("no formula provided to compute the model.matrix")
                }
            }
        }
    }
    output <- match.arg(output)
    if (! is.null(type)){
        if (! type %in% c("group", "global"))
            stop("type should be one of 'group' or 'local'")
    }
    .idx$nb <- 1:nrow(.idx)
    coefsubset <- intersect(names(beta), colnames(X))
    X <- X[, coefsubset, drop = FALSE]
    .idx$linpred <- as.numeric(crossprod(t(X[, coefsubset, drop = FALSE]), beta[coefsubset]))
            
    group_name <- idx_name(.idx, 2, 2)
    if (! is.null(group_name) & (is.null(type) || type == "group")){
        iv <- log(tapply(exp(.idx$linpred), list(dfidx::idx(.idx, 1), .idx[[group_name]]), sum))
        if (output == "obs"){
            iv <- data.frame(chid = rep(rownames(iv), each = ncol(iv)),
                             group = rep(colnames(iv), nrow(iv)),
                             iv = as.numeric(t(iv)))
            names(iv)[2] <- group_name
            iv <- merge(.idx, iv)
            iv <- iv[order(iv$nb), "iv"]
        }
    }
    else{
        #iv <- log(with(idx, tapply(exp(linpred), chid, sum))) bug
        # (for unbalanced set of choices) fixed thanks to Malick
        # Hossain
        iv <- log(tapply(exp(.idx$linpred), dfidx::idx(.idx, 1), sum, na.rm = TRUE))
        if (output == "obs"){
            iv <- data.frame(chid = rownames(iv), iv = as.numeric(iv))
            iv <- merge(.idx, iv)
            iv <- iv[order(iv$nb), "iv"]
        }
    }
    iv        
}

ltm <- function(x, to = c("vec", "mat", "ltm")){
    to <- match.arg(to)
    result <- x
    if (is.null(dim(x))){
        if (to != "vec"){
            z <- length(x)
            K <- - 0.5 + sqrt(1 + 8 * z) / 2
            if (abs(K - floor(K)) > 1E-07) stop("wrong length")
            result <- matrix(0, nrow = K, ncol = K)
            result[! lower.tri(result)] <- x
            result <- t(result)
            if (to == "mat") result[upper.tri(result)] <- t(result)[upper.tri(result)]
        }
    }
    else{
        if (! identical(x, t(x))){
            # the matrix is not symetric, its upper triangular
            # elements should be 0
            if (any(x[upper.tri(x)] != 0))
                stop("the matrix is not symetric, it should have only zero ont the upper triangular part")
            if (to == "mat") result[upper.tri(x)] <- t(x)[upper.tri(x)]
            if (to == "vec") result <- t(x)[! lower.tri(x)]
        }
        else{
            result <- x
            if (to == "ltm") result[upper.tri(result)] <- 0
            if (to == "vec") result <- t(x)[! lower.tri(x)]
        }
    }
    result
}
                  

Try the mlogit package in your browser

Any scripts or data that you put into this service are public.

mlogit documentation built on Oct. 23, 2020, 5:29 p.m.