vincentarelbundock / tinytable

Simple and Customizable Tables in `R`
https://vincentarelbundock.github.io/tinytable
GNU General Public License v3.0
196 stars 16 forks source link

Line breaks in footnotes #238

Closed vincentarelbundock closed 4 months ago

vincentarelbundock commented 5 months ago

I am having trouble with line breaks in footnotes. I applied the same logic from the tt note argument:

set.seed(1)

df <- tibble(
  x_1 = rnorm(100, 10, 3),
  x_2 = rnorm(100, 7, 2.5),
  u = rnorm(100, 0, 0.02),
  y = 1 + 2*x_1 - 3*x_2 + u
)

lm <- list(
  lm_1 = lm(y ~ x_1, data = df),
  lm_2 = lm(y ~ x_1 + x_2, data = df)
)

modelsummary(lm, note = "{A \\\\ B}")

when the PDF renders, the footnote is ipsis litteris "{A \\ B}". I tried using escape = F but then it won't compile, as well as changing output between tinytable and latex. what am I doing wrong this time?

Originally posted by @baarthur in https://github.com/vincentarelbundock/tinytable/issues/237#issuecomment-2089069665

vincentarelbundock commented 4 months ago

I'm not sure it if it is possible to do this in a tabularray table. The {A \\ B} appears to work only for cells, not for captions or notes. Here's the link to the tabularray documentation: https://ctan.math.illinois.edu/macros/latex/contrib/tabularray/tabularray.pdf

Or you could ask with a pure tabularray example (no tinytable code at all) on this website: https://tex.stackexchange.com/

If you find a solution, I would be very interested.

In the meantime, perhaps you can use several notes. Those should appear on different lines.

baarthur commented 4 months ago

What puzzles me is that I can use the {A \\ B} syntax in footnotes if I use tt().

I tried passing different notes as list items... But it would only work if I name the list, and I do not want the superscript identifiers ---in fact, it's not a matter of my taste but rather top-down impositions on how we need to write our theses in my university so that I necessarily need notes in the first line and source (even if I've done it myself) in the second. If it wasn't for this, I wouldn't bother very much with this issue.

vincentarelbundock commented 4 months ago

Oh, that's a good catch about tinytable. I had not noticed. Reopening.

baarthur commented 4 months ago

Just to clarify the above: modelsummary(lm, note = list("A", "B")) renders only the second comment in a PDF, but both comments in RStudio's viewer (HTML I suppose?):

image image
vincentarelbundock commented 4 months ago

Use a vector c() instead of a list list()

baarthur commented 4 months ago

Still won't work. Here I changed to modelsummary(lm, note = c("Lorem", "ipsum")) to facilitate finding it in the Latex output. See that "Lorem" is there, but isn't rendered unless I name the vector.

\begin{table}
\centering
\begin{talltblr}[         %% tabularray outer open
entry=none,label=none,
note{}={Lorem},
note{}={ipsum},
]                     %% tabularray outer close
{                     %% tabularray inner open
colspec={Q[]Q[]Q[]},
column{1}={halign=l,},
column{2}={halign=c,},
column{3}={halign=c,},
hline{8}={1,2,3}{solid, 0.05em, black},
}                     %% tabularray inner close
\toprule
& lm\_1 & lm\_2 \\ \midrule %% TinyTableHeader
(Intercept) & \num{-19.745}  & \num{1.002}   \\
& (\num{2.874})  & (\num{0.010}) \\
x\_1       & \num{2.003}    & \num{2.000}   \\
& (\num{0.269})  & (\num{0.001}) \\
x\_2       &                 & \num{-3.000}  \\
&                 & (\num{0.001}) \\
Num.Obs.    & \num{100}      & \num{100}     \\
R2          & \num{0.361}    & \num{1.000}   \\
R2 Adj.     & \num{0.354}    & \num{1.000}   \\
AIC         & \num{683.2}    & \num{-485.2}  \\
BIC         & \num{691.0}    & \num{-474.7}  \\
Log.Lik.    & \num{-338.593} & \num{246.582} \\
RMSE        & \num{7.15}     & \num{0.02}    \\
\bottomrule
\end{talltblr}
\end{table}
image
vincentarelbundock commented 4 months ago

I'll look into it, but it might take me a few weeks.

baarthur commented 4 months ago

sure thing, I understand. anyways, thanks for always providing fast answers!

baarthur commented 4 months ago

In the meantime, I developed a custom function—probably very inefficient, but it's what I can do—and it solves some of the issues I am facing using the did package altogether with modelsummary, so I share it here if anyone else wants to use it. Simple and full of limitations, but I was able to:

  1. Introduce line breaks in footnotes
  2. Print stars (related thread) depending on the confidence interval
  3. Order terms correctly when I provide a list of models --- a similar issue to https://github.com/vincentarelbundock/modelsummary/issues/135
  4. Finally, I don't know why the escape issue "came back" in my custom functions, so as a workaround I provide an option to rename the SE Cluster (and may I say, I find it better than having a variable name in the table).
get_aggte_summary <- function(model, round = 3, cluster_var = NULL, cluster_label = NULL) {
  est <- get_estimates(model) 
  gof <- get_gof(model) %>% 
    dplyr::mutate(N = as.character(nobs), `SE Clusters` = vcov.type, .keep = "none") %>% 
    {
      if(is.null(cluster_var)) {.} else {
        dplyr::mutate(., `SE Clusters` = stringr::str_replace(`SE Clusters`, cluster_var, 
                                                              cluster_label))
      }
    } %>% 
    tidyr::pivot_longer(cols = everything(), names_to = "term") %>% 
    dplyr::mutate(type = "gof")

  terms <- est$term

  row_groups <- terms %>% 
    purrr::imap(
      \(x, idx) ifelse(idx == 1, idx, 2*idx - 1)
    ) %>% 
    setNames(terms)

  est %>% 
    dplyr::select(term, estimate, conf.low, conf.high) %>% 
    dplyr::mutate(across(c(estimate, tidyselect::starts_with("conf")), \(x) round(x, round))) %>% 
    dplyr::mutate(estimate = ifelse(
      conf.high < 0 | conf.low > 0, paste0(estimate,"*"), as.character(estimate)
    )) %>% 
    dplyr::mutate(conf_int = paste0("[",conf.low,", ", conf.high, "]"), .keep = "unused") %>% 
    tidyr::pivot_longer(cols = c(estimate, conf_int), names_to = "type") %>% 
    dplyr::bind_rows(gof)
}

summary_aggte <- function(models, round = 3, cluster_var = NULL, cluster_label = NULL, ...) {

  summ <- if("list" %in% class(models)) {
    models %>% purrr::imap(
      \(x, idx) get_aggte_summary(x, round = round, cluster_var = cluster_var, 
                                  cluster_label = cluster_label) %>% 
        dplyr::rename({{idx}} := value)
    ) %>% 
      purrr::reduce(full_join, by = c("term", "type"))
  } else {
    get_aggte_summary(models, round = round, cluster_var = cluster_var, 
                      cluster_label = cluster_label)
  }

  summ %>%
    dplyr::mutate(order = ifelse(stringr::str_detect(term, "Average"), 1, 2)) %>% 
    dplyr::arrange(order, term, desc(type)) %>% 
    dplyr::mutate(term = stringr::str_extract(term, "Average|\\d+|N|SE Clusters")) %>% 
    dplyr::mutate(Cohort = ifelse(is.na(lag(term)), term, ifelse(lag(term) == term, "", term)), 
           .keep = "unused", .before = everything()) %>%
    dplyr::select(-c(type, order)) %>% 
    tinytable::tt(...) %>%
    tinytable::style_tt(i = nrow(.) - 1, line = "t")
}
vincentarelbundock commented 4 months ago

Thanks again for reporting this issue, and thanks for your patience. If you install both modelsummary and tinytable from Github, and then restart R completely, these commands should now work as expected:

packageVersion("tinytable")
packageVersion("modelsummary")

library(modelsummary)
library(tinytable)

mod <- lm(mpg ~ hp, mtcars)
modelsummary(mod, 
    notes = c(
        "does this work?",
        "oh {\\\\} yeah"),
    escape = FALSE
) 

tt(mtcars[1:4, 1:4],
    notes = c(
        "does this work?",
        "oh {\\\\} yeah"))
baarthur commented 4 months ago

packageVersion("tinytable") packageVersion("modelsummary")

library(modelsummary) library(tinytable)

mod <- lm(mpg ~ hp, mtcars) modelsummary(mod, notes = c( "does this work?", "oh {\\} yeah"), escape = FALSE )

tt(mtcars[1:4, 1:4], notes = c( "does this work?", "oh {\\} yeah"))

Working perfectly! thanks again for your support. btw:


packageVersion("tinytable")
[1] '0.2.1.16'
packageVersion("modelsummary")
[1] '2.0.0.10'