Skip to content

Commit e93c902

Browse files
authored
Merge pull request #13 from m-clark/fix-gam-cat-re
issue #12 (but opens up issue with new R CMD Checks for R development 4.1)
2 parents de4317e + be00355 commit e93c902

File tree

10 files changed

+114
-30
lines changed

10 files changed

+114
-30
lines changed

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Package: mixedup
22
Title: Miscellaneous functions for mixed models
3-
Version: 0.3.6
3+
Version: 0.3.7
44
Authors@R:
55
person(given = "Michael",
66
family = "Clark",

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# mixedup 0.3.7
2+
3+
* Extend exponentiate to summarize_model, extend extract_random_effects to multivariate models, minor fixes (e.g. issue #12).
4+
15
# mixedup 0.3.6
26

37
* Add group counts for extract_random_effects.

R/extract_random_coefs.R

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,19 @@
2525
#' \href{https://bbolker.github.io/mixedmodels-misc/glmmFAQ.html#confidence-intervals-on-conditional-meansblupsrandom-effects}{GLMM
2626
#' FAQ}. As noted there, this assumption may not be appropriate, and if you
2727
#' are really interested in an accurate uncertainty estimate you should
28-
#' probably use brms.
28+
#' probably use `brms`.
2929
#'
30-
#' The nlme package only provides the coefficients no estimated variance, so this
30+
#' For more complex models that include multiple outcomes/categories or have
31+
#' other anomalies, this function likely will not work at present, even if the
32+
#' underlying `extract_fixed_effects` and `extract_random_effects` do, as
33+
#' naming conventions are not consistent enough to deal with this without a
34+
#' lot of tedium that still may not satisfy every situation. I will possibly
35+
#' be able to update this in the future.
36+
#'
37+
#' The `nlme` package only provides the coefficients no estimated variance, so this
3138
#' function doesn't add to what you get from basic functionality for those
32-
#' models. In addition, nlme adds all random effects to the fixed effects,
33-
#' whereas lme4 and others only add the effects requested.
39+
#' models. In addition, `nlme` adds all random effects to the fixed effects,
40+
#' whereas `lme4` and others only add the effects requested.
3441
#'
3542
#' @return A data frame of the random coefficients and their standard errors.
3643
#'

R/extract_random_effects.R

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -575,16 +575,19 @@ extract_random_effects.gam <- function(
575575
}
576576
}
577577

578-
579578
if (purrr::is_empty(re_levels) | all(purrr::map_lgl(re_levels, is.null))) {
580579
stop('No factor random effects.')
581580
}
582581

582+
# can put an re smooth on a continuous covariate for penalization, but don't
583+
# want that in output
583584
non_factors <- purrr::map_lgl(re_levels, is.null)
585+
re_idx <- which(!non_factors)
584586

585587
# this test is covered but covr ignores for some reason
586588
if (any(non_factors)) {
587589
re_terms[non_factors] <- FALSE
590+
re_names <- re_names[re_idx]
588591
}
589592

590593
if (!is.null(re) && !re %in% re_names)
@@ -594,37 +597,58 @@ extract_random_effects.gam <- function(
594597
)
595598
)
596599

597-
re_labels <- purrr::map(model$smooth[re_terms], function(x) x$label)
600+
re_labels <- purrr::map(model$smooth[re_idx], function(x) x$label)
598601

599602
gam_coef <- stats::coef(model)
600603

601-
# issue, parenthesis in the names means problematic regex matching so remove
604+
# issue, parenthesis in the names means problematic regex matching, so remove
602605
# all but key part of pattern
603606
re_label_base <- gsub(re_labels, pattern = "s\\(", replacement = '') # remove first s
604607
re_label_base <- gsub(re_label_base, pattern = "\\(|\\)", replacement = '') # remove parenthesis
605608

606-
re_coef <- grepl(names(gam_coef), pattern = paste0('^s\\(', re_label_base, collapse = "|"))
609+
re_coef <- vector('list', length = length(re_idx))
610+
coef_idx <- vector('list', length = length(re_idx))
611+
612+
for (i in re_idx) {
613+
first_para <- model$smooth[[i]]$first.para
614+
last_para <- model$smooth[[i]]$last.para
607615

608-
re0 <- gam_coef[re_coef]
616+
coef_idx[[i]] <- first_para:last_para
617+
re_coef[[i]] <- gam_coef[coef_idx[[i]]]
618+
}
609619

610-
gam_se <- sqrt(diag(model$Vp)) # no names
611-
gam_se <- gam_se[names(gam_coef) %in% names(re0)]
620+
# extract coefs and se
621+
re0 <- unlist(re_coef)
622+
gam_se <- sqrt(diag(model$Vp))[unlist(coef_idx)]
612623

613-
# clean up names
624+
# clean up and gather names
614625
names(re0) <- gsub(names(re0), pattern = "s\\(|\\)", replacement = '')
615626
names(re0) <- gsub(names(re0), pattern = "\\.[0-9]+", replacement = '')
616627

617-
re_n <- dplyr::n_distinct(names(re0)) # possible use later
618-
re_names <- names(re0)
628+
re_names <- names(re0)
629+
re_effects <- purrr::map_chr(model$smooth, function(x) x$term[1])
630+
re_effects <- rep(re_effects, times = purrr::map_int(re_coef, length))
631+
632+
# check to see if factors are the smooth terms (i.e. random cat slope), and
633+
# repeat levels of grouping variable the number of levels in the factor
634+
for (i in re_idx) {
635+
smooth_vars <- model$smooth[[i]]$term
636+
smooth_term <- model$model[[smooth_vars[1]]] # it will be the first term, 2nd term is the RE var
637+
638+
if (length(smooth_vars) > 1 & (is.factor(smooth_term) | is.character(smooth_term))) {
639+
n_levs <- dplyr::n_distinct(smooth_term)
640+
re_levels[[i]] <- rep(re_levels[[i]], each = n_levs) # note the each, this is how mgcv orders and confirmed via lme4
641+
}
642+
}
619643

620-
random_effects <- dplyr::tibble(effect = re_names) %>%
644+
random_effects <- dplyr::tibble(group_var = re_names) %>%
621645
dplyr::mutate(
622-
group_var = split_group_effect(effect, which = 2),
623-
effect = split_group_effect(effect, which = 1),
646+
group_var = split_group_effect(group_var, which = 2),
647+
effect = re_effects,
624648
effect = ifelse(effect == group_var, 'Intercept', effect),
625-
group = unlist(re_levels),
626-
value = re0,
627-
se = gam_se
649+
group = unlist(re_levels),
650+
value = re0,
651+
se = gam_se
628652
)
629653

630654
if (add_group_N) {

codemeta.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"codeRepository": "https://m-clark.github.io/mixedup",
1111
"issueTracker": "https://github.com/m-clark/mixedup/issues",
1212
"license": "https://spdx.org/licenses/MIT",
13-
"version": "0.3.6",
13+
"version": "0.3.7",
1414
"programmingLanguage": {
1515
"@type": "ComputerLanguage",
1616
"name": "R",
@@ -218,7 +218,7 @@
218218
}
219219
],
220220
"releaseNotes": "https://github.com/m-clark/mixedup/blob/master/NEWS.md",
221-
"fileSize": "31823.321KB",
221+
"fileSize": "37466.782KB",
222222
"contIntegration": [
223223
"https://travis-ci.org/m-clark/mixedup",
224224
"https://ci.appveyor.com/project/m-clark/mixedup",

man/extract_random_coefs.Rd

Lines changed: 11 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
4.35 MB
Binary file not shown.

tests/testthat/helper-load_data.R

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,7 @@ brm_corCAR <- readRDS('brm_car_results.rds')
252252
#
253253
# bprior1 <- prior(student_t(5,0,10), class = b) +
254254
# prior(cauchy(0,2), class = sd)
255+
# pr = prior(normal(0, 10), class = b)
255256
#
256257
# brm_glm <- brm(
257258
# count ~ zAge + zBase * Trt + (1 | patient),
@@ -262,7 +263,7 @@ brm_corCAR <- readRDS('brm_car_results.rds')
262263
# thin = 40
263264
# )
264265
#
265-
# pr = prior(normal(0, 10), class = b)
266+
266267
#
267268
# brm_0 <-
268269
# brm(
@@ -321,9 +322,11 @@ brm_corCAR <- readRDS('brm_car_results.rds')
321322
# )
322323
#
323324
# probably problematic models but fine for testing
324-
325+
#
326+
#
327+
#
325328
### standard autocor
326-
329+
#
327330
# brm_corAR <- update(
328331
# brm_2,
329332
# autocor = cor_ar( ~ Days | Subject),
@@ -470,8 +473,27 @@ brm_corCAR <- readRDS('brm_car_results.rds')
470473
# cores = 4,
471474
# thin = 40,
472475
# )
476+
# brm_ordinal <- brm(
477+
# rating ~ period + carry + cs(treat) + (1|subject),
478+
# data = inhaler,
479+
# family = sratio("cloglog"),
480+
# prior = pr,
481+
# cores = 4,
482+
# thin = 40
483+
# )
484+
#
485+
# brm_categorical <- brm(
486+
# rating ~ period + carry + treat + (1|subject),
487+
# data = inhaler,
488+
# family = categorical,
489+
# prior = pr,
490+
# cores = 4,
491+
# thin = 40
492+
# )
473493
#
474494
# save(
495+
# brm_ordinal,
496+
# brm_categorical,
475497
# brm_sigma_simple,
476498
# brm_sigma,
477499
# brm_zi,
@@ -616,6 +638,17 @@ load('mgcv_results.RData')
616638
# data = glmmTMB::Salamanders
617639
# )
618640
#
641+
# gam_cat_slope <-
642+
# gam(
643+
# Reaction ~ Days + s(Subject, bs = "re") + s(Days, Subject, bs = "re"),
644+
# data = lme4::sleepstudy %>%
645+
# mutate(Days = factor(
646+
# case_when(Days < 2 ~ "x",
647+
# Days < 5 ~ "y",
648+
# TRUE ~ "z")
649+
# )
650+
# )
651+
# )
619652
#
620653
# bam objects are very large to save even for small models
621654
# bam_1 = bam(
@@ -632,6 +665,7 @@ load('mgcv_results.RData')
632665
# gam_1,
633666
# gam_2,
634667
# gam_3,
668+
# gam_cat_slope,
635669
# gam_glm,
636670
# bam_1,
637671
# file = 'tests/testthat/mgcv_results.RData'

tests/testthat/mgcv_results.RData

104 KB
Binary file not shown.

tests/testthat/test-extract_random_effects.R

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,14 @@ test_that('extract_random_effects.gam fails if no factors', {
347347

348348
})
349349

350+
test_that('extract_random_effects.gam can handle categorical slopes', {
351+
# cat_slope discretizes Days into 3 levels
352+
expect_equal(
353+
nrow(extract_random_effects(gam_cat_slope, re = 'Subject', add_group_N = TRUE, ci_level = .9)),
354+
nlevels(sleepstudy$Subject)*1 + nlevels(sleepstudy$Subject)*3
355+
)
356+
})
357+
350358
test_that('extract_random_effects.gam correct output', {
351359
expect_equal(
352360
nrow(extract_random_effects(gam_2, re = 'Subject')),

0 commit comments

Comments
 (0)