File: influence_mixed.R

package info (click to toggle)
r-cran-glmmtmb 1.1.10%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 7,124 kB
  • sloc: cpp: 1,506; sh: 16; makefile: 13
file content (225 lines) | stat: -rw-r--r-- 9,238 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# added 2017-12-13 by J. Fox
# 2017-12-14: improved recovery of model data
#             removed faulty one-step approximations
# 2018-01-28: fix computation of Cook's D for lme models
# 2018-05-23: fixed bug when more than one grouping variable (reported by Maarten Jung)
# 2018-06-07: skip plot of "sigma^2" in GLMM if dispersion fixed to 1; improved labelling for covariance components
# 2018-11-04: tweak to dfbetas.influence.merMod() suggested by Ben Bolker.
# 2018-11-09: parallel version of influence.merMod()
# 2020-02-17: works with tibble data (Ben Bolker/Joseph O'Brien)

## influence diagnostics for mixed models

## copied from car (unexported, don't want to rely on car:::combineLists)
combineLists <- function(..., fmatrix="list", flist="c", fvector="rbind", 
                         fdf="rbind", recurse=FALSE){
    # combine lists of the same structure elementwise
    
    # ...: a list of lists, or several lists, each of the same structure
    # fmatrix: name of function to apply to matrix elements
    # flist: name of function to apply to list elements
    # fvector: name of function to apply to data frame elements
    # recurse: process list element recursively
    
    frecurse <- function(...){
        combineLists(..., fmatrix=fmatrix, fvector=fvector, fdf=fdf, 
                     recurse=TRUE)
    }
    
    if (recurse) flist="frecurse"
    list.of.lists <- list(...)
    if (length(list.of.lists) == 1){
        list.of.lists <- list.of.lists[[1]]
        list.of.lists[c("fmatrix", "flist", "fvector", "fdf")] <- 
            c(fmatrix, flist, fvector, fdf)
        return(do.call("combineLists", list.of.lists))
    }
    if (any(!sapply(list.of.lists, is.list))) 
        stop("arguments are not all lists")
    len <- sapply(list.of.lists, length)
    if (any(len[1] != len)) stop("lists are not all of the same length")
    nms <- lapply(list.of.lists, names)
    if (any(unlist(lapply(nms, "!=", nms[[1]])))) 
        stop("lists do not all have elements of the same names")
    nms <- nms[[1]]
    result <- vector(len[1], mode="list")
    names(result) <- nms
    for(element in nms){
        element.list <- lapply(list.of.lists, "[[", element)
#        clss <- sapply(element.list, class)
        clss <- lapply(element.list, class)
#        if (any(clss[1] != clss)) stop("list elements named '", element,
        if (!all(vapply(clss, function(e) all(e == clss[[1L]]), NA)))
          stop("list elements named '", element, "' are not all of the same class")
        
        is.df <- is.data.frame(element.list[[1]])
        fn <- if (is.matrix(element.list[[1]])) fmatrix 
        else if (is.list(element.list[[1]]) && !is.df) flist 
        else if (is.vector(element.list[[1]])) fvector
        else if (is.df) fdf
        else stop("list elements named '", element, 
                  "' are not matrices, lists, vectors, or data frames")
        result[[element]] <- do.call(fn, element.list)
    }
    result
}

## copied from lme4 
namedList <- function (...)  {
    L <- list(...)
    snm <- sapply(substitute(list(...)), deparse)[-1]
    if (is.null(nm <- names(L))) 
        nm <- snm
    if (any(nonames <- nm == "")) 
        nm[nonames] <- snm[nonames]
    setNames(L, nm)
}

##' compute influence measures for [g]lmerMod (from lme4) or glmmTMB (from glmmTMB) fitted models
##' @param model fitted model
##' @param groups (character) names of group(s) to leave out/compute sensitivity for: ".case"  means observation-level sensitivity
##' @param data data frame to use in refitting (will be taken from the model call if possible, but it may be necessary to specify this if the influence measures are being computed in a new environment)
##' @param maxfun maximum number of function evaluations
##' @param ncores number of parallel cores to use for computation
##' @param component for glmmTMB models, which component to use (conditional, zero-inflated, or dispersion)
##' @examples
##' source(system.file("other_methods","influence_mixed.R",package="glmmTMB"))
##' m1 <- glmmTMB(count ~ mined + (1|site),
##'   zi=~mined,
##'   family=poisson, data=Salamanders)
##' i1 <- influence_mixed(m1,groups="site")
##' ## check that it works with tibbles too ...
##' m2 <- update(m1, data=tibble::as_tibble(Salamanders))
##' i2 <- influence_mixed(m2,groups="site")
##' if (require(car)) {
##'    stopifnot(all.equal(dfbeta(i1),dfbeta(i2)))
##' }
influence_mixed <- function(model, groups=".case", data, maxfun=1000,
                            ncores=getOption("mc.cores", 2L),
                            component=c("cond","zi","disp"),
                            progress=FALSE,
                            ...) {
    component <- match.arg(component)
    if (inherits(model,"glmmTMB")) {
        fe <- function(x) fixef(x)[[component]]
        VC <- function(x) VarCorr(x)[[component]]
        .vcov <- function(x) Matrix::as.matrix(vcov(x)[[component]])
    } else {
        fe <- fixef
        VC <- VarCorr
        .vcov <- function(x) Matrix::as.matrix(vcov(x))
    }
    
    if (is.infinite(ncores)) {
        ncores <- parallel::detectCores(logical=FALSE)
    }
    if (missing(data)){
        data <- getCall(model)$data
        data <- if (!is.null(data)) eval(data, parent.frame())
        else stop("model did not use the data argument")
    }
    if (".case" %in% groups) {
        data$.case <- rownames(data)
    }
    else if (length(groups) > 1){
        del.var <- paste0(groups, collapse=".")
        ## Reduce(interaction,groups) ?
        data[[del.var]] <- apply(data, 1, paste0, collapse=".")
        groups <- del.var
    }
    unique.del <- unique(data[[groups]])
    data[[".groups"]] <- data[[groups]]
    par <- list(theta=getME(model, "theta"))
    if (inherits(model, "glmerMod") || inherits(model,"glmmTMB")) {
        par$beta <- fe(model)
    }
    fixed <- fe(model)
    Vs <- VC(model)
    nms <- names(Vs)
    sep <- ":"
    if (length(nms) == 1) {
        nms <- ""
        sep <- ""
    }
    vc <- sigma(model)^2
    names(vc) <- "sigma^2"
    for (i in 1:length(Vs)){
        V <- Vs[[i]]
        c.names <- colnames(V)
        e.names <- outer(c.names, c.names, function(a, b) paste0("C[", a, ",", b, "]"))
        diag(e.names) <- paste0("v[", c.names, "]")
        v <- V[lower.tri(V, diag=TRUE)]
        names(v) <- paste0(nms[i], sep, e.names[lower.tri(e.names, diag=TRUE)])
        vc <- c(vc, v)
    }
    if (inherits(model,"lmerMod")) {
        control <- lme4::lmerControl(optCtrl=list(maxfun=maxfun))
    } else if (inherits(model, "glmerMod")) {
        control <- lme4:: glmerControl(optCtrl=list(maxfun=maxfun))
    } else if (inherits(model, "glmmTMB")) {
        ## ???
        control <- glmmTMBControl(optCtrl=list(iter.max=maxfun,eval.max=maxfun))
    }
    deleteGroup <- function(del){
        if (progress) cat(".")
        data_sub <- data[data$.groups!=del,]
        mod.1 <- suppressWarnings(update(model, data=data_sub,
                                         start=par,
                                         control=control))
        ## platform-independent?
        if (inherits(mod.1,"glmmTMB")) {
            feval <- mod.1$fit$evaluations[["function"]]
            converged <- mod.1$fit$convergence==0
        } else { ## [g]lmerMod
            opt <- mod.1@optinfo
            feval <- opt$feval
            converged <- opt$conv$opt == 0 && length(opt$warnings) == 0
        }
        fixed.1 <- fe(mod.1)
        Vs.1 <- VC(mod.1)
        vc.0 <- sigma(mod.1)^2
        for (V in Vs.1){
            vc.0 <- c(vc.0, V[lower.tri(V, diag=TRUE)])
        }
        vc.1 <- vc.0
        vcov.1 <<- .vcov(mod.1)
        namedList(fixed.1, vc.1, vcov.1, converged, feval)
    }
    result <- if(ncores >= 2){
        message("using a cluster of ", ncores, " cores")
        cl <- parallel::makeCluster(ncores)
        on.exit(parallel::stopCluster(cl))
        parallel::clusterExport(cl, c("namedList", "combineLists"))
        parallel::clusterEvalQ(cl, require("lme4"))
        parallel::clusterEvalQ(cl, require("glmmTMB"))
        parallel::clusterApply(cl, unique.del, deleteGroup)
    } else {
        lapply(unique.del, deleteGroup)
    }
    result <- combineLists(result)
    fixed.1 <- result$fixed.1
    rownames(fixed.1) <- unique.del
    colnames(fixed.1) <- names(fixed)
    vc.1 <- result$vc.1
    rownames(vc.1) <- unique.del
    colnames(vc.1) <- names(vc)
    feval <- as.vector(result$feval)
    converged <- as.vector(result$converged)
    vcov.1 <- result$vcov.1
    names(vcov.1) <- names(feval) <- names(converged) <- unique.del
    left <- "[-"
    right <- "]"
    if (groups == ".case") {
        groups <- "case"
    }
    nms <- c("fixed.effects", paste0("fixed.effects", left, groups, right),
             "var.cov.comps", paste0("var.cov.comps", left, groups, right),
             "vcov", paste0("vcov", left, groups, right),
             "groups", "deleted", "converged", "function.evals")
    result <- list(fixed, fixed.1, vc, vc.1,
                   .vcov(model), vcov.1, groups, unique.del, converged, feval)
    names(result) <- nms
    ## call this influence.merMod since all methods are currently written for it
    class(result) <- "influence.merMod"
    result
}