Skip to content

Commit 377fe9f

Browse files
Anya DeCarloAnya DeCarlo
authored andcommitted
Add fallback for lmer objects in pool() without requiring broom.mixed
Problem: pool() fails when used with lmer/lmerMod objects unless the broom.mixed package is installed. This forces users to install an entire additional package just to extract coefficients that are already accessible through lme4's built-in fixef() and vcov() methods. Solution: Added a tryCatch wrapper in summary.mira() that detects when tidy() fails for lmerMod objects and falls back to manual coefficient extraction using: - lme4::fixef() for fixed effects estimates - stats::vcov() for variance-covariance matrix - Standard error calculation via sqrt(diag(vcov())) This produces identical results to broom.mixed (verified in test_verify_accuracy.R with zero numerical difference) while eliminating the dependency. Benefits: - Reduces package dependencies and installation overhead - Improves security by avoiding unnecessary package installations - Maintains backward compatibility (still works with broom.mixed if installed) - Uses only built-in methods already present in lme4 and base R Testing: Run test_lmer_fix.R to verify pool() works without broom.mixed Run test_verify_accuracy.R to verify results match broom.mixed exactly Fixes common error: 'No tidy method for objects of class lmerMod' Related to issues about broom.mixed dependency requirements
1 parent 0ae532e commit 377fe9f

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

R/summary.R

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,29 @@ summary.mira <- function(object,
2323
type <- match.arg(type)
2424
fitlist <- getfit(object)
2525
if (type == "tidy") {
26-
v <- lapply(fitlist, tidy, effects = "fixed", parametric = TRUE, ...) %>% bind_rows()
26+
# Try standard tidy() first, with fallback for lmer objects
27+
v <- tryCatch({
28+
lapply(fitlist, tidy, effects = "fixed", parametric = TRUE, ...) %>% bind_rows()
29+
}, error = function(e) {
30+
# Check if this is an lmerMod object without broom.mixed
31+
if (inherits(fitlist[[1]], "lmerMod") &&
32+
grepl("No.*tidy.*method", e$message, ignore.case = TRUE)) {
33+
# Manual extraction for lmer objects using built-in methods
34+
lapply(fitlist, function(fit) {
35+
coefs <- lme4::fixef(fit)
36+
se <- sqrt(diag(as.matrix(stats::vcov(fit))))
37+
data.frame(
38+
term = names(coefs),
39+
estimate = as.numeric(coefs),
40+
std.error = as.numeric(se),
41+
stringsAsFactors = FALSE
42+
)
43+
}) %>% bind_rows()
44+
} else {
45+
# Re-throw the error if it's not an lmer issue
46+
stop(e)
47+
}
48+
})
2749
}
2850
if (type == "glance") {
2951
v <- lapply(fitlist, glance, ...) %>% bind_rows()

test_lmer_fix.R

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Test script for lmer pooling fix
2+
# This tests whether pool() works with lmer objects without broom.mixed
3+
4+
library(mice)
5+
library(lme4)
6+
7+
# Create simple test data with missing values
8+
set.seed(123)
9+
n <- 100
10+
test_data <- data.frame(
11+
id = rep(1:20, each = 5),
12+
x = rnorm(n),
13+
y = rnorm(n)
14+
)
15+
16+
# Introduce some missingness
17+
test_data$y[sample(1:n, 20)] <- NA
18+
19+
# Impute
20+
cat("Running imputation...\n")
21+
imp <- mice(test_data, m = 5, print = FALSE, seed = 456)
22+
23+
# Fit mixed model
24+
cat("Fitting mixed models to imputed data...\n")
25+
fit <- with(imp, lmer(y ~ x + (1 | id)))
26+
27+
# Try to pool - this should work now without broom.mixed!
28+
cat("\nAttempting to pool results (without broom.mixed)...\n")
29+
pooled <- pool(fit)
30+
31+
cat("\n=== SUCCESS! ===\n")
32+
cat("Pooled results:\n")
33+
print(summary(pooled))
34+
35+
cat("\n✅ The fix works! pool() can now handle lmer objects without broom.mixed\n")

test_verify_accuracy.R

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# Verification test: Compare our manual extraction vs broom.mixed
2+
# This proves we're getting the EXACT same numbers
3+
4+
library(mice)
5+
library(lme4)
6+
7+
# Create test data
8+
set.seed(123)
9+
n <- 100
10+
test_data <- data.frame(
11+
id = rep(1:20, each = 5),
12+
x = rnorm(n),
13+
y = rnorm(n)
14+
)
15+
test_data$y[sample(1:n, 20)] <- NA
16+
17+
# Impute
18+
cat("Running imputation...\n")
19+
imp <- mice(test_data, m = 5, print = FALSE, seed = 456)
20+
21+
# Fit mixed model
22+
cat("Fitting mixed models...\n")
23+
fit <- with(imp, lmer(y ~ x + (1 | id)))
24+
25+
cat("\n=== METHOD 1: Our Fix (without broom.mixed) ===\n")
26+
pooled_our_fix <- pool(fit)
27+
print(summary(pooled_our_fix))
28+
29+
cat("\n=== METHOD 2: With broom.mixed (the old way) ===\n")
30+
library(broom.mixed)
31+
pooled_broom <- pool(fit)
32+
print(summary(pooled_broom))
33+
34+
cat("\n=== COMPARISON ===\n")
35+
our_results <- summary(pooled_our_fix)
36+
broom_results <- summary(pooled_broom)
37+
38+
cat("\nIntercept estimate difference:",
39+
abs(our_results$estimate[1] - broom_results$estimate[1]), "\n")
40+
cat("Intercept SE difference:",
41+
abs(our_results$std.error[1] - broom_results$std.error[1]), "\n")
42+
cat("x coefficient estimate difference:",
43+
abs(our_results$estimate[2] - broom_results$estimate[2]), "\n")
44+
cat("x coefficient SE difference:",
45+
abs(our_results$std.error[2] - broom_results$std.error[2]), "\n")
46+
47+
# Check if they're identical (within floating point precision)
48+
if (all.equal(our_results$estimate, broom_results$estimate, tolerance = 1e-10) == TRUE &&
49+
all.equal(our_results$std.error, broom_results$std.error, tolerance = 1e-10) == TRUE) {
50+
cat("\n✅ PERFECT MATCH! Our fix produces IDENTICAL results to broom.mixed\n")
51+
cat("We're not making up numbers - we're using the exact same math!\n")
52+
} else {
53+
cat("\n❌ WARNING: Results differ!\n")
54+
}
55+
56+
cat("\n=== MANUAL VERIFICATION ===\n")
57+
cat("Let's also manually check one imputation to prove the math:\n\n")
58+
single_fit <- getfit(fit, 1)
59+
cat("Manual fixef() extraction:\n")
60+
print(lme4::fixef(single_fit))
61+
cat("\nManual vcov() extraction (standard errors):\n")
62+
print(sqrt(diag(vcov(single_fit))))

0 commit comments

Comments
 (0)