honestlm keeps familiar linear model workflows, but adds
guardrails around common interpretation traps.
library(honestlm)
#> honestlm: use honest_lm() for guarded linear model summaries, or as_honest_lm(lm(...)) for existing lm objects.Summaries
honest_lm() fits a regular lm() object with
an extra class. The estimates are ordinary linear-model estimates, but
the printed summary treats coefficient p-values cautiously.
fit <- honest_lm(mpg ~ wt + factor(cyl), data = mtcars)
summary(fit)
#>
#> Call:
#> lm(formula = mpg ~ wt + factor(cyl), data = mtcars)
#>
#> Residuals:
#> Min 1Q Median 3Q Max
#> -4.5890 -1.2357 -0.5159 1.3845 5.7915
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 33.9908 1.8878 18.006 NA
#> wt -3.2056 0.7539 -4.252 0.000213 ***
#> factor(cyl)6 -4.2556 1.3861 -3.070 NA
#> factor(cyl)8 -6.0709 1.6523 -3.674 NA
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Categorical predictors:
#> factor(cyl): 3 levels; reference level = 4
#>
#> Notes:
#> * Intercept p-values are hidden because they usually test whether the expected response is zero at the reference condition. Use intercept_p_value = TRUE if you really want them.
#> * When present, p-values are shown for continuous predictors and two-level categorical predictors.
#> * P-values for multi-level categorical coefficient rows are hidden by default because those rows compare levels to a reference level, not whether the overall predictor matters.
#> * For post-hoc comparisons among factor levels, consider estimated marginal means, e.g. emmeans::emmeans() and pairs(). See https://rvlenth.github.io/emmeans/.
#>
#> Residual standard error: 2.557 on 28 degrees of freedom
#> Multiple R-squared: 0.8374, Adjusted R-squared: 0.82
#> F-statistic: 48.08 on 3 and 28 DF (model-level p-value hidden)By default, summary() shows p-values for continuous
predictors and two-level categorical predictors, but prints
NA for intercept p-values and multi-level categorical
contrast rows. Those multi-level rows are comparisons to a reference
level, not separate tests of whether each category, or the whole
predictor, matters. For post-hoc comparisons among levels, estimated
marginal means are usually a better tool.
Sequential sums of squares
Base anova() for a single linear model with more than
one predictor reports sequential Type I sums of squares.
honestlm stops by default because those tables depend on
the order of terms in the formula. For term-level tests, use
car::Anova(model, type = 2) or use Type III sums of squares
with care.
anova(fit)
#> Error:
#> ! `anova()` for a single linear model with more than one predictor reports sequential Type I sums of squares. honestlm stops here because changing the order of terms can change the table. Use `car::Anova(model, type = 2)` for term-level tests, or type = 3 with care. If you really want Type I sums of squares, call `anova(model, beg = TRUE)`.If you really want the Type I table, you can ask for it explicitly.
anova(fit, beg = TRUE)
#> Analysis of Variance Table
#>
#> Response: mpg
#> Df Sum Sq Mean Sq F value Pr(>F)
#> wt 1 847.73 847.73 129.6650 5.079e-12 ***
#> factor(cyl) 2 95.26 47.63 7.2856 0.002835 **
#> Residuals 28 183.06 6.54
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> Warning: `anova()` for linear models reports sequential Type I sums of squares.
#> Those depend on the order of terms in the formula and are usually not the test
#> you want for models with multiple predictors. Use `car::Anova()` for Type II or
#> Type III sums of squares.Broom output
If broom is installed, tidy() removes
p.value by default and adds a contrast_note
column for factor contrast rows.
broom::tidy(fit)
#> # A tibble: 4 × 6
#> term estimate std.error statistic p.value contrast_note
#> <chr> <dbl> <dbl> <dbl> <dbl> <chr>
#> 1 (Intercept) 34.0 1.89 18.0 NA NA
#> 2 wt -3.21 0.754 -4.25 0.000213 NA
#> 3 factor(cyl)6 -4.26 1.39 -3.07 NA comparison to reference l…
#> 4 factor(cyl)8 -6.07 1.65 -3.67 NA comparison to reference l…Model-aware plots
geom_lm_smooth() and stat_lm_means() are
designed to make plots match the linear model structure being
taught.
library(ggplot2)
ggplot(mtcars, aes(wt, mpg, colour = factor(cyl))) +
geom_point() +
geom_lm_smooth(interaction = FALSE)
ggplot(mtcars, aes(factor(cyl), mpg, colour = factor(am))) +
geom_point() +
stat_lm_means(interaction = FALSE)
Set interaction = TRUE when you want separate slopes or
full cell means.