Skip to content
Merged
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: equatiomatic
Title: Transform Models into 'LaTeX' Equations
Version: 0.4.5
Version: 0.4.6
Authors@R:
c(person(given = "Daniel",
family = "Anderson",
Expand Down
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# equatiomatic 0.4.6

* Equation for logit glm models now allows for a compact form with `logit_notation = TRUE`. Thanks Michael Friendly for the suggestion.

* `preview_eq()` now can preview lists of equations.

# equatiomatic 0.4.5

* Correction in a bug in `.labels()` that caused incorrect labels for polynomial
Expand Down
31 changes: 19 additions & 12 deletions R/extract_eq.R
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
#' @param se_subscripts Logical. If \code{se_subscripts = TRUE} then the
#' equation will include the standard errors below each coefficient.
#' This is supported for lm and glm models.
#' @param logit_notation Logical. If \code{TRUE}, then the equation uses a more
#' compact notation for a logistic regression.
#' @param ... Additional arguments (for future development; not currently used).
#' @export
#'
Expand Down Expand Up @@ -167,6 +169,8 @@
#' )
#' mod5 <- glm(out ~ ., data = d, family = binomial(link = "logit"))
#' extract_eq(mod5, wrap = TRUE)
#' # More compact logit notation
#' extract_eq(mod5, wrap = TRUE, logit_notation = TRUE)
extract_eq <- function(model, intercept = "alpha", greek = "beta",
greek_colors = NULL, subscript_colors = NULL,
var_colors = NULL, var_subscript_colors = NULL,
Expand All @@ -179,7 +183,7 @@ extract_eq <- function(model, intercept = "alpha", greek = "beta",
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE, ...) {
UseMethod("extract_eq", model)
}

Expand All @@ -201,13 +205,14 @@ extract_eq.default <- function(model, intercept = "alpha", greek = "beta",
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE,
...) {
if (index_factors & use_coefs) {
stop("Coefficient estimates cannot be returned when factors are indexed.")
}

lhs <- extract_lhs(model, ital_vars, show_distribution, use_coefs,
swap_var_names, var_colors)
swap_var_names, var_colors, logit_notation = logit_notation)
rhs <- extract_rhs(model, index_factors)

eq_raw <- create_eq(
Expand Down Expand Up @@ -342,7 +347,8 @@ extract_eq.lmerMod <- function(model, intercept = "alpha", greek = "beta",
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE,
...) {
if (isTRUE(se_subscripts)) {
warning("Standard errors are not supported for mixed effects models",
call. = FALSE
Expand Down Expand Up @@ -422,7 +428,8 @@ extract_eq.glmerMod <- function(model, intercept = "alpha", greek = "beta",
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE,
...) {
if (!is.null(greek_colors)) {
warning(
paste0("Colorization of greek notation not currently ",
Expand Down Expand Up @@ -461,7 +468,7 @@ extract_eq.forecast_ARIMA <- function(model, intercept = "alpha", greek = "beta"
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE, ...) {

if (isTRUE(se_subscripts)) {
warning("Standard errors are not supported for mixed effects models",
Expand Down Expand Up @@ -620,7 +627,7 @@ extract_eq.model_fit <-
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE, ...) {

if ("fit" %in% names(model)) {
fitted_model <- model$fit
Expand All @@ -641,7 +648,7 @@ extract_eq.model_fit <-
fix_signs = fix_signs, font_size = font_size,
mean_separate = mean_separate,
return_variances = return_variances,
se_subscripts = se_subscripts, ...)
se_subscripts = se_subscripts, logit_notation = logit_notation, ...)
} else {
stop("The 'model' does not appear to be a proper **model_fit** object ",
"because it does not have a 'fit' component.")
Expand All @@ -663,7 +670,7 @@ extract_eq.workflow <-
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE, ...) {

if ("fit" %in% names(model)) {
fitted_stage <- model$fit
Expand All @@ -686,7 +693,7 @@ extract_eq.workflow <-
fix_signs = fix_signs, font_size = font_size,
mean_separate = mean_separate,
return_variances = return_variances,
se_subscripts = se_subscripts, ...)
se_subscripts = se_subscripts, logit_notation = logit_notation, ...)
} else {
stop("The 'model' does not appear to be a proper **workflow** object ",
"because it does not have a proper 'fit' component.")
Expand Down Expand Up @@ -714,7 +721,7 @@ extract_eq.list <- function(model, intercept = "alpha", greek = "beta",
use_coefs = FALSE, coef_digits = 2,
fix_signs = TRUE, font_size = NULL,
mean_separate = NULL, return_variances = FALSE,
se_subscripts = FALSE, ...) {
se_subscripts = FALSE, logit_notation = FALSE, ...) {

res <- sapply(model, extract_eq, intercept = intercept, greek = greek,
greek_colors = greek_colors, subscript_colors = subscript_colors,
Expand All @@ -733,7 +740,7 @@ extract_eq.list <- function(model, intercept = "alpha", greek = "beta",
fix_signs = fix_signs, font_size = font_size,
mean_separate = mean_separate,
return_variances = return_variances,
se_subscripts = se_subscripts, ...)
se_subscripts = se_subscripts, logit_notation = logit_notation, ...)
res <- as.character(res) # Make sure it is a character vector
names(res) <- names(model)
class(res) <- c("equation", "character")
Expand Down
75 changes: 48 additions & 27 deletions R/extract_lhs.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ extract_lhs <- function(model, ...) {
#' @noRd

extract_lhs.lm <- function(model, ital_vars, show_distribution, use_coefs,
swap_var_names, var_colors, ...) {
swap_var_names, var_colors, logit_notation = FALSE,
...) {

check_dots(...)
lhs <- rownames(attr(model$terms, "factors"))[1]
Expand Down Expand Up @@ -67,7 +68,7 @@ extract_lhs.summary.lm <- extract_lhs.lm
#' @return A character string
#' @noRd
extract_lhs.lmerMod <- function(model, ital_vars, use_coefs, swap_var_names,
var_colors, ...) {
var_colors, logit_notation = FALSE,...) {
check_dots(...)
lhs <- all.vars(formula(model))[1]
lhs_nm <- lhs
Expand Down Expand Up @@ -99,7 +100,7 @@ extract_lhs.lmerMod <- function(model, ital_vars, use_coefs, swap_var_names,
#' @return A character string
#' @noRd
extract_lhs.glmerMod <- function(model, ital_vars, use_coefs, ...) {
extract_lhs.lmerMod(model, ital_vars, use_coefs, ...)
extract_lhs.lmerMod(model, ital_vars, use_coefs, logit_notation = FALSE, ...)
}

#' Extract left-hand side of a glm object
Expand All @@ -115,11 +116,14 @@ extract_lhs.glmerMod <- function(model, ital_vars, use_coefs, ...) {
#' @noRd

extract_lhs.glm <- function(model, ital_vars, show_distribution, use_coefs,
swap_var_names, var_colors,...) {
swap_var_names, var_colors, logit_notation = FALSE, ...) {

if (show_distribution) {
if (model$family$family == "binomial") {
if (!isFALSE(logit_notation))
message("logit_notation = TRUE ignored when show_distribution is TRUE.")
return(extract_lhs2_binomial(model, ital_vars, use_coefs,
swap_var_names, var_colors))
swap_var_names, var_colors, logit_notation = logit_notation))
} else {
message("This distribution is not presently supported; the distribution assumption
will not be displayed")
Expand All @@ -133,7 +137,7 @@ extract_lhs.glm <- function(model, ital_vars, show_distribution, use_coefs,

lhs_escaped <- escape_tex(lhs)

if(!is.null(var_colors)) {
if (!is.null(var_colors)) {
names(lhs) <- lhs_nm
names(lhs_escaped) <- lhs_nm
lhs_escaped <- colorize_terms(var_colors, list(lhs), list(lhs_escaped))
Expand All @@ -150,7 +154,7 @@ extract_lhs.glm <- function(model, ital_vars, show_distribution, use_coefs,
}
if (model$family$family == "binomial") {
return(extract_lhs_binomial(model, ital_vars, use_coefs,
swap_var_names, var_colors))
swap_var_names, var_colors, logit_notation = logit_notation))
} else {
lhs <- all.vars(formula(model))[1]
lhs_nm <- lhs
Expand All @@ -162,7 +166,7 @@ extract_lhs.glm <- function(model, ital_vars, show_distribution, use_coefs,

lhs_escaped <- escape_tex(lhs)

if(!is.null(var_colors)) {
if (!is.null(var_colors)) {
names(lhs) <- lhs_nm
names(lhs_escaped) <- lhs_nm
lhs_escaped <- colorize_terms(var_colors, list(lhs), list(lhs_escaped))
Expand All @@ -183,7 +187,8 @@ extract_lhs.glm <- function(model, ital_vars, show_distribution, use_coefs,
#' @noRd

extract_lhs_binomial <- function(model, ital_vars, use_coefs,
swap_var_names, var_colors) {
swap_var_names, var_colors, logit_notation = FALSE) {

outcome <- all.vars(formula(model))[1]
outcome_nm <- outcome
names(outcome) <- outcome_nm
Expand All @@ -192,15 +197,13 @@ extract_lhs_binomial <- function(model, ital_vars, use_coefs,
outcome <- swap_names(swap_var_names, outcome)[[1]]
}

outcome_escaped <- escape_tex(outcome)
outcome_escaped <- add_tex_ital_v(outcome_escaped, ital_vars)

# This returns a 1x1 data.frame
ss <- model$data[which(model$y == 1)[1], outcome_nm]

# Convert to single character
ss <- as.character(unlist(ss))

outcome_escaped <- escape_tex(outcome)
outcome_escaped <- add_tex_ital_v(outcome_escaped, ital_vars)

ss_escaped <- escape_tex(ss)
ss_escaped <- add_tex_ital_v(ss_escaped, ital_vars)

Expand All @@ -213,29 +216,47 @@ extract_lhs_binomial <- function(model, ital_vars, use_coefs,
names(ss_escaped) <- outcome_nm
ss_escaped <- colorize_terms(var_colors, list(outcome), list(ss_escaped))
}

if (is.na(ss)) {
full_lhs <- paste("P(", outcome_escaped, ")")

if (isTRUE(logit_notation)) {
if (use_coefs) {
full_lhs <- paste0(
"\\operatorname{logit}\\left[ P(\\hat{",
outcome_escaped,
"}) \\right]"
)
} else {
full_lhs <- paste0(
"\\operatorname{logit}\\left[ P(",
outcome_escaped,
") \\right]"
)
}
} else {
full_lhs <- paste(
"P(", outcome_escaped,
"=",
ss_escaped, ")"
)
}
if (use_coefs) {
full_lhs <- add_hat(full_lhs)
if (is.na(ss)) {
full_lhs <- paste("P(", outcome_escaped, ")")
} else {
full_lhs <- paste(
"P(", outcome_escaped,
"=",
ss_escaped, ")"
)
}
if (use_coefs) {
full_lhs <- add_hat(full_lhs)
}
}

full_lhs <- modify_lhs_for_link(model, full_lhs)
if (!isTRUE(logit_notation))
full_lhs <- modify_lhs_for_link(model, full_lhs)
class(full_lhs) <- c("character", class(model))
Comment thread
phgrosjean marked this conversation as resolved.
full_lhs
}

#' @keywords internal
#' @noRd
extract_lhs2_binomial <- function(model, ital_vars, use_coefs, swap_var_names,
var_colors) {
var_colors, logit_notation = FALSE) {

outcome <- all.vars(formula(model))[1]
outcome_nm <- outcome
names(outcome) <- outcome_nm
Expand Down
2 changes: 1 addition & 1 deletion R/print.R
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ preview_eq <- function(x, ...) {
stop("Pandoc is not available. Please install it to use this function.",
call. = FALSE)

if (!inherits(x, "equation"))
if (!inherits(x, c("equation", "list")))
x <- extract_eq(x)

rmd <- tempfile(fileext = ".Rmd")
Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
is (re)defined here.

- Check the two last tests in test-lmerMod.R that produce different roundings
on a silicon processor in macOS. For now these tests are skipped.
on several systems, and with R 4.6.0. For now these tests are skipped.

- Implement the Greek characters colorization for `lme4::lmer()` and
`lme4::glmer()`.
Expand Down
6 changes: 6 additions & 0 deletions man/extract_eq.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions tests/testthat/_snaps/glm.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,36 @@
\end{aligned}
$$

# logit_notation works for logistic glm

Code
extract_eq(model_logit, logit_notation = TRUE)
Output
$$
\operatorname{logit}\left[ P(\operatorname{outcome}) \right] = \alpha + \beta_{1}(\operatorname{categorical}_{\operatorname{b}}) + \beta_{2}(\operatorname{categorical}_{\operatorname{c}}) + \beta_{3}(\operatorname{continuous\_1}) + \beta_{4}(\operatorname{continuous\_2})
$$

---

Code
extract_eq(model_logit, logit_notation = TRUE, wrap = TRUE)
Output
$$
\begin{aligned}
\operatorname{logit}\left[ P(\operatorname{outcome}) \right] &= \alpha + \beta_{1}(\operatorname{categorical}_{\operatorname{b}}) + \beta_{2}(\operatorname{categorical}_{\operatorname{c}}) + \beta_{3}(\operatorname{continuous\_1})\ + \\
&\quad \beta_{4}(\operatorname{continuous\_2})
\end{aligned}
$$

---

Code
extract_eq(model_logit, logit_notation = TRUE, use_coefs = TRUE)
Output
$$
\operatorname{logit}\left[ P(\hat{\operatorname{outcome}}) \right] = 5.33 + 0.33(\operatorname{categorical}_{\operatorname{b}}) - 0.12(\operatorname{categorical}_{\operatorname{c}}) - 0.06(\operatorname{continuous\_1}) + 0.01(\operatorname{continuous\_2})
$$

# Renaming Variables works

$$
Expand Down
Loading
Loading