Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); GitHub - datalorax/equatiomatic: Convert models to LaTeX equations · GitHub
Skip to content

Repository files navigation

equatiomatic

lifecycleR build statusCodecov test coverageCovrpage Summary

The goal of {equatiomatic} is to reduce the pain associated with writing LaTeX code from a fitted model. The package aims to support any model supported by {broom}. See the introduction to equatiomatic for currently supported models.

Installation

Install from CRAN with:

install.packages("equatiomatic")

Or get the development version from GitHub with:

remotes::install_github("datalorax/equatiomatic")

Basic usage

The gif above shows the basic functionality.

To convert a model to LaTeX, feed a model object to extract_eq():

library(equatiomatic)
# Fit a simple modelmod1<- lm(mpg~cyl+disp, data=mtcars)
# Give the results to extract_eq
extract_eq(mod1)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \epsilon$$

The model can be built in any standard way. It can handle shortcut syntax:

mod2<- lm(mpg~., data=mtcars)
extract_eq(mod2)
#> $$
#> \operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> $$

$$\operatorname{mpg} = \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon$$

When using categorical variables, it will include the levels of the variables as subscripts.

data("penguins", package="equatiomatic")
mod3<- lm(body_mass_g~bill_length_mm+species, data=penguins)
extract_eq(mod3)
#> $$
#> \operatorname{body\_mass\_g} = \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon
#> $$

$$\operatorname{body_mass_g} = \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}}) + \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) + \epsilon$$

It helpfully preserves the order the variables are supplied in the formula:

set.seed(8675309)
d<-data.frame(cat1= rep(letters[1:3], 100),
cat2= rep(LETTERS[1:3], each=100),
cont1= rnorm(300, 100, 1),
cont2= rnorm(300, 50, 5),
out= rnorm(300, 10, 0.5))
mod4<- lm(out~cont1+cat2+cont2+cat1, data=d)
extract_eq(mod4)
#> $$
#> \operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon
#> $$

$$\operatorname{out} = \alpha + \beta_{1}(\operatorname{cont1}) + \beta_{2}(\operatorname{cat2}_{\operatorname{B}}) + \beta_{3}(\operatorname{cat2}_{\operatorname{C}}) + \beta_{4}(\operatorname{cont2}) + \beta_{5}(\operatorname{cat1}_{\operatorname{b}}) + \beta_{6}(\operatorname{cat1}_{\operatorname{c}}) + \epsilon$$

Appearance

You can wrap the equations so that a specified number of terms appear on the right-hand side of the equation using terms_per_line (defaults to 4):

extract_eq(mod2, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\
#> &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\
#> &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\ + \\ &\quad \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\ + \\ &\quad \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

extract_eq(mod2, wrap=TRUE, terms_per_line=6)
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\
#> &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp}) + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt})\ + \\ &\quad \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs}) + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

When wrapping, you can change whether the lines end with trailing math operators like + (the default), or if they should begin with them using operator_location = "end" or operator_location = "start":

extract_eq(mod2, wrap=TRUE, terms_per_line=4, operator_location="start")
#> $$
#> \begin{aligned}
#> \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\
#> &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\
#> &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{mpg} &= \alpha + \beta_{1}(\operatorname{cyl}) + \beta_{2}(\operatorname{disp}) + \beta_{3}(\operatorname{hp})\\ &\quad + \beta_{4}(\operatorname{drat}) + \beta_{5}(\operatorname{wt}) + \beta_{6}(\operatorname{qsec}) + \beta_{7}(\operatorname{vs})\\ &\quad + \beta_{8}(\operatorname{am}) + \beta_{9}(\operatorname{gear}) + \beta_{10}(\operatorname{carb}) + \epsilon \end{aligned}$$

By default, all text in the equation is wrapped in \operatorname{}. You can optionally have the variables themselves be italicized (i.e. not be wrapped in \operatorname{}) with ital_vars = TRUE:

extract_eq(mod2, wrap=TRUE, ital_vars=TRUE)
#> $$
#> \begin{aligned}
#> mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\
#> &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\
#> &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon
#> \end{aligned}
#> $$

$$\begin{aligned} mpg &= \alpha + \beta_{1}(cyl) + \beta_{2}(disp) + \beta_{3}(hp)\ + \\ &\quad \beta_{4}(drat) + \beta_{5}(wt) + \beta_{6}(qsec) + \beta_{7}(vs)\ + \\ &\quad \beta_{8}(am) + \beta_{9}(gear) + \beta_{10}(carb) + \epsilon \end{aligned}$$

R Markdown and previewing

If you include extract_eq() in an R Markdown chunk, {knitr} will render the equation. If you’d like to see the LaTeX code wrap the call in print().

You can also use the preview_eq() function to preview the equation in RStudio:

preview_eq(mod1)

Both extract_eq() and preview_eq() work with base R or {magrittr} pipes, so you can do something like this:

#library(magrittr) # if you want to use %>% instead of |>
extract_eq(mod1) |>
preview_eq()
# Or simply: preview_eq(mod1)

Extra options

There are several extra options you can enable with additional arguments to extract_eq().

Actual coefficients

You can return actual numeric coefficients instead of Greek letters with use_coefs = TRUE:

extract_eq(mod1, use_coefs=TRUE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 - 1.59(\operatorname{cyl}) - 0.02(\operatorname{disp})$$

By default, it will remove doubled operators like “+ -”, but you can keep those in (which is often useful for teaching) with fix_signs = FALSE:

extract_eq(mod1, use_coefs=TRUE, fix_signs=FALSE)
#> $$
#> \operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})
#> $$

$$\operatorname{\widehat{mpg}} = 34.66 + -1.59(\operatorname{cyl}) + -0.02(\operatorname{disp})$$

This works in longer wrapped equations:

extract_eq(mod2, wrap=TRUE, terms_per_line=3, use_coefs=TRUE,
fix_signs=FALSE)
#> $$
#> \begin{aligned}
#> \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\
#> &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\
#> &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\
#> &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb})
#> \end{aligned}
#> $$

$$\begin{aligned} \operatorname{\widehat{mpg}} &= 12.3 + -0.11(\operatorname{cyl}) + 0.01(\operatorname{disp})\ + \\ &\quad -0.02(\operatorname{hp}) + 0.79(\operatorname{drat}) + -3.72(\operatorname{wt})\ + \\ &\quad 0.82(\operatorname{qsec}) + 0.32(\operatorname{vs}) + 2.52(\operatorname{am})\ + \\ &\quad 0.66(\operatorname{gear}) + -0.2(\operatorname{carb}) \end{aligned}$$

Beyond lm()

You’re not limited to just lm models! {equatiomatic} supports many other models, including logistic regression, probit regression, and ordered logistic regression (with MASS::polr()).

Logistic regression with glm()

model_logit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="logit"))
extract_eq(model_logit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &= \alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{sex} = \operatorname{male} ) }{ 1 - P( \operatorname{sex} = \operatorname{male} ) } \right] &amp;= \alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\quad \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}}) \end{aligned}$$

Probit regression with glm()

model_probit<- glm(sex~bill_length_mm+species, data=penguins,
family= binomial(link="probit"))
extract_eq(model_probit, wrap=TRUE, terms_per_line=3)
#> $$
#> \begin{aligned}
#> P( \operatorname{sex} = \operatorname{male} ) &= \Phi[\alpha + \beta_{1}(\operatorname{bill\_length\_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\
#> &\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{sex} = \operatorname{male} ) &amp;= \Phi[\alpha + \beta_{1}(\operatorname{bill_length_mm}) + \beta_{2}(\operatorname{species}_{\operatorname{Chinstrap}})\ + \\ &amp;\qquad\ \beta_{3}(\operatorname{species}_{\operatorname{Gentoo}})] \end{aligned}$$

Ordered logistic regression with MASS::polr()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 100, 1),
continuous_2= rnorm(300, 50, 5))
model_ologit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="logistic")
model_oprobit<-MASS::polr(outcome~continuous_1+continuous_2,
data=df, Hess=TRUE, method="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Ordered regression (logit and probit) with ordinal::clm()

set.seed(1234)
df<-data.frame(
outcome= ordered(rep(LETTERS[1:3], 100), levels=LETTERS[1:3]),
continuous_1= rnorm(300, 1, 1),
continuous_2= rnorm(300, 5, 5))
model_ologit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="logit")
model_oprobit<-ordinal::clm(outcome~continuous_1+continuous_2,
data=df, link="probit")
extract_eq(model_ologit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2}) \\
#> \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})
#> \end{aligned}
#> $$

$$\begin{aligned} \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{A} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{A} ) } \right] &= \alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \\ \log\left[ \frac { P( \operatorname{outcome} \leq \operatorname{B} ) }{ 1 - P( \operatorname{outcome} \leq \operatorname{B} ) } \right] &= \alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2}) \end{aligned}$$

extract_eq(model_oprobit, wrap=TRUE)
#> $$
#> \begin{aligned}
#> P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})] \\
#> P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous\_1}) + \beta_{2}(\operatorname{continuous\_2})]
#> \end{aligned}
#> $$

$$\begin{aligned} P( \operatorname{outcome} \leq \operatorname{A} ) &= \Phi[\alpha_{1} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \\ P( \operatorname{outcome} \leq \operatorname{B} ) &= \Phi[\alpha_{2} + \beta_{1}(\operatorname{continuous_1}) + \beta_{2}(\operatorname{continuous_2})] \end{aligned}$$

Extension

If you would like to contribute to this package, we’d love your help! We are particularly interested in extending to more models. We hope to support any model supported by {broom} in the future.

Code of Conduct

Please note that the ‘equatiomatic’ project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

A note of appreciation

We’d like to thank the authors of the {palmerpenguins} dataset for generously allowing us to incorporate the penguins dataset in our package for example usage.

Horst AM, Hill AP, Gorman KB (2020). palmerpenguins: Palmer Archipelago (Antarctica) penguin data. R package version 0.1.0. https://allisonhorst.github.io/palmerpenguins/

About

Convert models to LaTeX equations

Resources

Code of conduct

Stars

626 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages