Adding coefficient group titles to modelsummary output - r

I'm trying to group coefficients together in a modelsummary output table and add row titles for these groups :
library(modelsummary)
ols1 <- lm(mpg ~ cyl + disp + hp,
data = mtcars,
na.action = na.omit
)
modelsummary(ols1,
title = "Table 1",
stars = TRUE
)
The modelsummary documentation (https://cran.r-project.org/web/packages/modelsummary/modelsummary.pdf) suggests this might be something to do with the shape and group_map arguments, but I can't really figure out how to use them.
Any guidance would be very helpful, thanks!

When the documentation mentions “groups”, it refers to models like multinomial logits where each predictor has one coefficient per outcome level. In this example, the “group” column is called “response”:
library(nnet)
library(modelsummary)
mod <- multinom(cyl ~ mpg + hp, data = mtcars, trace = FALSE)
modelsummary(
mod,
output = "markdown",
shape = response ~ model)
Model 1
(Intercept)
6
0.500
(41.760)
8
8.400
(0.502)
mpg
6
-83.069
(416.777)
8
-120.167
(508.775)
hp
6
16.230
(81.808)
8
20.307
(87.777)
Num.Obs.
32
R2
1.000
R2 Adj.
0.971
AIC
12.0
BIC
20.8
RMSE
0.00
What you probably mean is something different: Adding manual labels to sets of coefficients. This is easy to achieve because modelsummary() produces a kableExtra or a gt table which can be customized in infinite ways.
https://cran.r-project.org/web/packages/kableExtra/vignettes/awesome_table_in_html.html
For example, you may want to look at the group_rows function from kableExtra:
library(kableExtra)
mod <- lm(mpg ~ cyl + disp + hp, data = mtcars)
modelsummary(mod) |>
group_rows(index = c("Uninteresting" = 4,
"Interesting" = 4,
"Other" = 7))

Related

How to transpose a regression output with modelsummary package?

I JUST found out about this amazing R package, modelsummary.
It doesn't seem like it offers an ability to transpose regression outputs.
I know that you cannot do a tranposition within kable-extra, which is my go-to for ordinary table outputs in R. Since modelsummary relies on kable-extra for post-processing, I'm wondering if this is possible. Has anyone else figured it out?
Ideally I'd like to preserve the stars of my regression output.
This is available in STATA (below):
Thanks in advance!
You can flip the order of the terms in the group argument formula. See documentation here and also here for many examples.
library(modelsummary)
mod <- list(
lm(mpg ~ hp, mtcars),
lm(mpg ~ hp + drat, mtcars))
modelsummary(mod, group = model ~ term)
(Intercept)
hp
drat
Model 1
30.099
-0.068
(1.634)
(0.010)
Model 2
10.790
-0.052
4.698
(5.078)
(0.009)
(1.192)
The main problem with this strategy is that there is not (yet) an automatic way to append goodness of fit statistics. So you would probably have to rig something up by creating a data.frame and feeding it to the add_columns argument. For example:
N <- sapply(mod, function(x) get_gof(x)$nobs)
N <- data.frame(N = c(N[1], "", N[2], ""))
modelsummary(mod,
group = model ~ term,
add_columns = N,
align = "lcccc")
(Intercept)
hp
drat
N
Model 1
30.099
-0.068
32
(1.634)
(0.010)
Model 2
10.790
-0.052
4.698
32
(5.078)
(0.009)
(1.192)
If you have ideas about the best default behavior for goodness of fit statistics, please file a feature request on Github.

Best way to report multiple regression models on several dimensions (evolving model formulation and year of data)

Situation
I am fitting a series of evolving regression models. For the purposes of this question, we can think of these models in terms of Model A, Model B, and Model C. All models share at least one same covariate.
I am also fitting these models for two separate years of data. Again, for the purposes of this question, the years will be 2000 and 2010.
In an attempt to simplify the reporting of results, I am attempting to combine the reporting of the regressions into a single table that would have some kind of the following format:
2000 2010
Model A
Coef Ex1
Model B
Coef Ex1
Coef Ex2
Model C
Coef Ex1
Coef Ex2
Coef Ex3
The idea being that someone can look quickly at Coef Ex1 across several models and years.
What Have I Tried
I have tried to achieve the above table using both R stargazer and kable packages. With stargazer I can get the fully formatted table for a single model formulation across many years (e.g., stargazer(modelA2000, modelA2010), but I cannot figure out how to stack additional model formulations on the rows.
For kable I have been able to stack horizontal models, but I have not been able to add in additional years (e.g., coefs <- bind_rows(tidy(modelA2000), tidy(modelB2000), tidy(modelC2000)); coefs %>% kable()).
Question: how can I use stargazer or kable to report evolving regression models (which share the same covariates) in the rows but also with year of cross section on the column? I think I can somehow extend the answer posted here, although I'm not sure how.
Reproducible example
# Load the data
mtcars <- mtcars
# Create example results for models A, B, and C for 2000
modelA2000 <- lm(mpg ~ cyl, data = mtcars)
modelB2000 <- lm(mpg ~ cyl + wt, data = mtcars)
modelC2000 <- lm(mpg ~ cyl + wt + disp, data = mtcars)
# Slightly modify data for second set of results
mtcars$cyl <- mtcars$cyl*runif(1)
# Fit second set of results. Same models, pretending it's a different year.
modelA2010 <- lm(mpg ~ cyl, data = mtcars)
modelB2010 <- lm(mpg ~ cyl + wt, data = mtcars)
modelC2010 <- lm(mpg ~ cyl + wt + disp, data = mtcars)
Two notes before starting:
You want a pretty "custom" table, so it is almost inevitable that some manual operations will be required.
My answer relies on the development version of modelsummary, which you can install like this:
library(remotes)
install_github("vincentarelbundock/modelsummary")
We will need 4 concepts, many of them related to the broom package:
broom::tidy a function that takes a statistical model and returns a data.frame of estimates with one row per coefficient.
broom::glance a function that takes a statistical model and returns a one-row data.frame with model characteristics (e.g., number of observations)
modelsummary_list a list with 2 elements called "tidy" and "glance", and with a class name of "modelsummary_list".
The modelsummary package allows you to draw regression tables. Under the hood, it uses broom::tidy and broom::glance to extract information from those models. Users can also supply their own information about a model by supplying a list to which we assign the class modelsummary_list, as documented here.
EDIT: The recommended way to do this in modelsummary is now to use the group argument. Scroll to the end of this post for illustrative code.
Obsolete example with useful discussion
The modelsummary_wide is a function that was initially designed to "stack" results from several models with several groups of coefficients. This is useful for things like multinomial models, but it also helps us in your case, where you have multiple models in multiple groups (here: years).
First, we load packages, tweak the data, and estimate our models:
library(modelsummary)
library(broom)
library(dplyr)
mtcars2010 <- mtcars
mtcars2010$cyl <- mtcars$cyl * runif(1)
models <- list(
"A" = list(
lm(mpg ~ cyl, data = mtcars),
lm(mpg ~ cyl, data = mtcars2010)),
"B" = list(
lm(mpg ~ cyl + wt, data = mtcars),
lm(mpg ~ cyl + wt, data = mtcars2010)),
"C" = list(
lm(mpg ~ cyl + wt + disp, data = mtcars),
lm(mpg ~ cyl + wt + disp, data = mtcars2010)))
Notice that we saved our models in three groups, in a list of list.
Then, we define a tidy_model function that accepts a list of two models (one per year), combines the information on those two models, and creates a modelsummary_list object (again, please refer to the documentation). Note that we assign the "year" information to a "group" column in the tidy object.
We apply this function to each of our three groups of models using lapply.
tidy_model <- function(model_list) {
# tidy estimates
tidy_2000 <- broom::tidy(model_list[[1]])
tidy_2010 <- broom::tidy(model_list[[2]])
# create a "group" column
tidy_2000$group <- 2000
tidy_2010$group <- 2010
ti <- bind_rows(tidy_2000, tidy_2010)
# glance estimates
gl <- data.frame("N" = stats::nobs(model_list[[1]]))
# output
out <- list(tidy = ti, glance = gl)
class(out) <- "modelsummary_list"
return(out)
}
models <- lapply(models, tidy_model)
Finally, we call the modelsummary_wide with the stacking="vertical" argument to obtain this table:
modelsummary_wide(models, stacking = "vertical")
Of course, the table can be adjusted, coefficients renamed, etc. using the other arguments of the modelsummary_wide function or with kableExtra or some other package supported by the output argument.
More modern example without detailed explanation
library("modelsummary")
library("broom")
library("quantreg")
mtcars2010 <- mtcars
mtcars2010$cyl <- mtcars$cyl * runif(1)
models <- list(
"A" = list(
"2000" = rq(mpg ~ cyl, data = mtcars),
"2010" = rq(mpg ~ cyl, data = mtcars2010)),
"B" = list(
"2000" = rq(mpg ~ cyl + wt, data = mtcars),
"2010" = rq(mpg ~ cyl + wt, data = mtcars2010)),
"C" = list(
"2000" = rq(mpg ~ cyl + wt + disp, data = mtcars),
"2010" = rq(mpg ~ cyl + wt + disp, data = mtcars2010)))
tidy_model <- function(model_list) {
# tidy estimates
tidy_2000 <- broom::tidy(model_list[[1]])
tidy_2010 <- broom::tidy(model_list[[2]])
# create a "group" column
tidy_2000$group <- "2000"
tidy_2010$group <- "2010"
ti <- bind_rows(tidy_2000, tidy_2010)
# output
out <- list(tidy = ti, glance = data.frame("nobs 2010" = length(model_list[[1]]$fitted.values)))
class(out) <- "modelsummary_list"
return(out)
}
models <- lapply(models, tidy_model)
modelsummary(models,
group = model + term ~ group,
statistic = "conf.int")
2000
2010
A
(Intercept)
36.800
36.800
[30.034, 42.403]
[30.034, 42.403]
cyl
-2.700
-67.944
[-3.465, -1.792]
[-87.204, -45.102]
B
(Intercept)
38.871
38.871
[30.972, 42.896]
[30.972, 42.896]
cyl
-1.743
-43.858
[-2.154, -0.535]
[-54.215, -13.472]
wt
-2.679
-2.679
[-5.313, -1.531]
[-5.313, -1.531]
C
(Intercept)
40.683
40.683
[31.235, 47.507]
[31.235, 47.507]
cyl
-1.993
-50.162
[-3.137, -1.322]
[-78.948, -33.258]
wt
-2.937
-2.937
[-5.443, -1.362]
[-5.443, -1.362]
disp
0.003
0.003
[-0.009, 0.035]
[-0.009, 0.035]

using "at" argument of margins function in R for logit model

I want to be able to analyze the marginal effect of continuous and binary variables in a logit model. I am hoping for R to provide what the independent marginal effect of hp is at its mean (in this example that is at 200), while also finding the marginal effect of the vs variable equaling 1. I am hoping the output table also includes the SE, p value, and z score. I am having trouble with the table and when I have gotten it to run it doesn't evaluate the two variables independently. Here is an MRE below. Thank you!
mod2 <- glm(am ~ hp + factor(vs), data=mtcars, family=binomial)
margins(mod2)
#> Average marginal effects
#> glm(formula = am ~ hp + factor(vs), family = binomial, data = mtcars)
#> hp vs1
#> -0.00203 -0.03154
#code where I am trying to evaluate at the desired values.
margins(mod2, at=list(hp=200, vs=1))
This is because you've changed vs to a factor.
Consider the following
library(margins)
mod3 <- glm(am ~ hp + vs, data=mtcars, family=binomial)
margins(mod3, at=list(hp=200, vs=1))
# Average marginal effects at specified values
# glm(formula = am ~ hp + vs, family = binomial, data = mtcars)
#
# at(hp) at(vs) hp vs
# 200 1 -0.001783 -0.02803
There is no real reason to turn vs into a factor here; it's dichotomous.

Using column name of dataframe as predictor variable in linear regression

I'm trying to loop through all the column names of my data.frame and use them
as predictor variable in a linear regression.
What I currently have is:
for (i in 1:11){
for (j in 1:11){
if (i != j ){
var1 = names(newData)[i]
var2 = names(newData)[j]
glm.fit = glm(re78 ~ as.name(var1):as.name(var2), data=newData)
summary(glm.fit)
cv.glm(newData, glm.fit, K = 10)$delta[1]
}
}
}
Where newData is my data.frame and there are 11 columns in total. This code gives me the following error:
Error in model.frame.default(formula = re78 ~ as.name(var1), data = newData, :
invalid type (symbol) for variable 'as.name(var1)'
How can I fix this, and make it work?
It looks like you want models that use all combinations of two variables. Here's another way to do that using the built-in mtcars data frame for illustration and using mpg as the outcome variable.
We get all combinations of two variables (excluding the outcome variable, mpg in this case) using combn. combn returns a list where each list element is a vector containing the names of a pair of variables. Then we use map (from the purrr package) to create models for each pair of variables and store the results in a list.
We use reformulate to construct the model formula. .x refers back to the vectors of variables names (each element of vars). If you run, for example, reformulate(paste(c("cyl", "disp"),collapse="*"), "mpg"), you can see what reformulate is doing.
library(purrr)
# Get all combinations of two variables
vars = combn(names(mtcars)[-grep("mpg", names(mtcars))], 2, simplify=FALSE)
Now we want to run regression models on all pairs of variables and store results in a list:
# No interaction
models = map(vars, ~ glm(reformulate(.x, "mpg"), data=mtcars))
# Interaction only (no main effects)
models = map(vars, ~ glm(reformulate(paste(.x, collapse=":"), "mpg"), data=mtcars))
# Interaction and main effects
models = map(vars, ~ glm(reformulate(paste(.x, collapse="*"), "mpg"), data=mtcars))
Name each list element with the formula for that model:
names(models) = map(models, ~ .x[["terms"]])
To create the model formulas using paste instead of reformulate you could do (change + to : or *, depending on what combination of interactions and main effects you want to include):
models = map(vars, ~ glm(paste("mpg ~", paste(.x, collapse=" + ")), data=mtcars))
To see how paste is being used here, you can run:
paste("mpg ~", paste(c("cyl", "disp"), collapse=" * "))
Here's what the first two models look like when the models include both main effects and the interaction:
models[1:2]
$`mpg ~ cyl * disp`
Call: glm(formula = reformulate(paste(.x, collapse = "*"), "mpg"),
data = mtcars)
Coefficients:
(Intercept) cyl disp cyl:disp
49.03721 -3.40524 -0.14553 0.01585
Degrees of Freedom: 31 Total (i.e. Null); 28 Residual
Null Deviance: 1126
Residual Deviance: 198.1 AIC: 159.1
$`mpg ~ cyl * hp`
Call: glm(formula = reformulate(paste(.x, collapse = "*"), "mpg"),
data = mtcars)
Coefficients:
(Intercept) cyl hp cyl:hp
50.75121 -4.11914 -0.17068 0.01974
Degrees of Freedom: 31 Total (i.e. Null); 28 Residual
Null Deviance: 1126
Residual Deviance: 247.6 AIC: 166.3
To assess model output, you can use functions from the broom package. The code below returns data frames with, respectively, the coefficients and performance statistics for each model.
library(broom)
model_coefs = map_df(models, tidy, .id="Model")
model_performance = map_df(models, glance, .id="Model")
Here are what the results look like for models with both main effects and the interaction:
head(model_coefs, 8)
Model term estimate std.error statistic p.value
1 mpg ~ cyl * disp (Intercept) 49.03721186 5.004636297 9.798357 1.506091e-10
2 mpg ~ cyl * disp cyl -3.40524372 0.840189015 -4.052950 3.645320e-04
3 mpg ~ cyl * disp disp -0.14552575 0.040002465 -3.637919 1.099280e-03
4 mpg ~ cyl * disp cyl:disp 0.01585388 0.004947824 3.204212 3.369023e-03
5 mpg ~ cyl * hp (Intercept) 50.75120716 6.511685614 7.793866 1.724224e-08
6 mpg ~ cyl * hp cyl -4.11913952 0.988229081 -4.168203 2.672495e-04
7 mpg ~ cyl * hp hp -0.17068010 0.069101555 -2.469989 1.987035e-02
8 mpg ~ cyl * hp cyl:hp 0.01973741 0.008810871 2.240120 3.320219e-02
You can use fit <- glm(as.formula(paste0("re78 ~ ", var1)), data=newData) as #akrun suggest. Further, you likely do not want to call your object glm.fit as there is a function with the same.
Caveat: I do not why you have the double loop and the :. Do you not want a regression with a single covaraite? I have no idea what you are trying to achieve otherwise.

create a variable to replace variables in a model in R

When writing statistic model, I usually use a lot of co-variables to adjust the model, so I need to rewrite the variable again and again. Even though I could copy and paste, the model looks very long. Could I create a variable which could replace many variables? E.g.:
fm <- lm(y ~ a+b+c+d+e, data)
I could create a variable like: model1 = a+b+c+d+e, then the model looks like:
fm <-lm(y ~ model1, data)
I tried many ways, but it did successful, like model1 <- c(a+b+c+d),
Could someone help me with this?
how about saving it as a formula?
model <- ~a+b+c+d
You can then extract the terms using terms, or update the formula using update
Example:
model <- mpg ~ disp + wt + cyl
lm(model, mtcars)
## Call:
## lm(formula = model, data = mtcars)
##
## Coefficients:
## (Intercept) disp wt cyl
## 41.107678 0.007473 -3.635677 -1.784944
model <- update(model, ~. + qsec)
lm(model, mtcars)
## Call:
## lm(formula = model, data = mtcars)
##
## Coefficients:
## (Intercept) disp wt cyl qsec
## 30.17771 0.01029 -4.55318 -1.24109 0.55277
Edit:
As Kristoffer Winther Balling mentioned in the comments, a cleverer way to do this is to save the formula as a string (e.g. "mpg ~ disp + wt + cyl") and then use as.formula. You can then use familiar paste or other string manipulation functions to change the formula.

Resources