library(tidyverse)
library(knitr)
library(kableExtra)
kable(head(mpg)) %>% kable_styling %>% add_header_above(c(" " = 6,
"foobar" = 5))
Works well when you want to specify the grouped column header manually:
However I want to put this feature inside a function, I've tried:
a_function <- function(my_column_head_name){
kable(head(mpg)) %>% kable_styling %>% add_header_above(c(" " = 6,
my_column_head_name = 5))
}
a_function("foobar")
But this returns the named argument as the column header name:
I'm guessing this is something to do with lazy evaluation?
The header is a named character vector with colspan as values. You have to create the vector and assign names with names().
a_function <- function(my_column_head_name){
myHeader <- c(" " = 6, my_column_head_name = 5)
names(myHeader) <- c(" ", my_column_head_name)
kable(head(mpg)) %>% kable_styling %>% add_header_above(myHeader)
}
a_function("foobar")
Related
In kableExtra >= 0.8.0, the canonical way to insert a linebreak into text piped into a table from a kableExtra function such as add_header_above or pack_rows is to add an \n directly.
However, this appears not to work with the escape = FALSE argument, which is required if the text also contains LaTeX code.
How can one force linebreaks in kableExtra functions with escape = FALSE?
library(dplyr)
library(knitr)
library(kableExtra)
starwars %>%
filter(species == 'Gungan' | species == 'Droid') %>%
arrange(species) %>%
select(name, eye_color) %>%
kbl(booktabs = TRUE) %>%
pack_rows(
index = c(
'The droids: everybody\'s favourite' = 6,
'The Gungans: only beloved of \nthose aged under $3^2$' = 3),
escape = FALSE)
ISSUE
The issue at hand is that you wish to escape part of your header (i.e., the break) and not escape another part (i.e., the math code).
Further Complications
This core issue is further complicated by a number of factors:
when and how kableExtra is programmed to deal with escaping
a desire to have a solution that works for both html and LaTeX output
when and how R evaluates code
A SOLUTION
Here is a solution that will work for both html and LaTeX output, but it is not as clean and straight forward as your original code:
# a new version of `kableExtra::linebreak()` that takes into account what type
# of output is desired as well as how much escaping is necessary
linebreak2 <- function(x, double_escape = TRUE, ...) {
# if LaTeX insert text into a `\makecell[]{}` command and double escape
if(knitr::is_latex_output())
return(linebreak(x, double_escape = double_escape, ...))
# if html output just replace `\n`s with `<br/>`s
if(knitr::is_html_output())
return(gsub("\n", "<br/>", x))
# let x pass through for other types of output
return(x)
}
# build the index named vector outside the pipe flow
# in order to set the names using `linebreak2()`
index <- c(6, 3)
names(index) <- c(
'The droids: everybody\'s favourite',
linebreak2('The Gungans: only beloved of \nthose aged under $3^2$')
)
# proceed as before
starwars %>%
filter(species == 'Gungan' | species == 'Droid') %>%
arrange(species) %>%
select(name, eye_color) %>%
kbl(booktabs = TRUE) %>%
pack_rows(index = index, escape = FALSE)
PDF Output
HTML Output
You could use html line break tag <br/>:
starwars %>%
filter(species == 'Gungan' | species == 'Droid') %>%
arrange(species) %>%
select(name, eye_color) %>%
kbl(booktabs = TRUE) %>%
pack_rows(
index = c(
'The droids: everybody\'s favourite' = 6,
'The Gungans: only beloved of <br/> those aged under $3^2$' = 3),
escape = FALSE)
Using gt() you can make the title include information from the data frame in addition to text:
table_name <- paste(data$`column_data`[1],paste("Sample Text"))
table <- gt(data,
rownames_to_stub = TRUE) %>%
tab_header(title = table_name)
Is there a way to make KableExtra do this?
Yes, by supplying a data frame to the header argument of add_header_above() function. According to the documentation of this argument:
Alternatively, a data frame with two columns can be provided: The first column should
contain the header names (character vector) and the second column
should contain the colspan (numeric vector).
A {gt} table
mtcars |>
head(c(5,4)) |>
gt(rownames_to_stub = TRUE) |>
tab_header(paste(mtcars$drat[1], paste("Sample Text")))
The result:
A {kableExtra} table
mtcars |>
head(c(5,4)) |>
kable() |>
kable_styling(full_width = FALSE) |>
add_header_above(
data.frame(
text = paste(mtcars$drat[1],"Sample Text"),
span = 5)) # The number 5 is used to match the number of columns
The result:
I have a moderate-sized data set that is 1000 rows by 81 columns. I'd like to use the output from str(), but I'd like to present it in a "prettier" way. I've tried things like this:
df %>% str() %>% kableExtra::kbl() %>% kableExtra::kable_minimal()
and
tbl_summary(as.data.frame(str(df)))
but neither works. I'm not married to str() or to any specific package, but that's the kind of summary I'm going for.
In the end, this is intended to generate an HTML file, but I'd like it to work with PDF output as well.
Any ideas on how to do this?
Update II:
This can be achieved making use of this gist devtools::source_gist('4a0a5ab9fe7e1cf3be0e')
<devtools::source_gist('4a0a5ab9fe7e1cf3be0e')>
print(strtable(iris, factor.values=as.integer), na.print='') %>%
kable() %>%
htmlTable()
Update I:
you could extend:
data.frame(variable = names(iris),
class = sapply(iris, typeof),
levels = sapply(iris, class),
first_values = sapply(iris, function(x) paste0(head(x), collapse = ", ")),
levels_values = sapply(iris, function(x) paste0(unique(x), collapse =", ")),
row.names = NULL) %>%
kable() %>%
htmlTable()
First answer:
Something like this using iris dataset:
library(knitr)
library(magrittr)
library(htmlTable)
data.frame(variable = names(iris),
classe = sapply(iris, typeof),
first_values = sapply(iris, function(x) paste0(head(x), collapse = ", ")),
row.names = NULL) %>%
kable() %>%
htmlTable()
skimr and gt (or kable, or flextable, or DT, or many other table packages) could also work here:
mtcars |>
skimr::skim() |>
gt::gt()
I use kableExtra for producing several tables and I'd like to use a function instead of repeating all the code. But with my limited knowledge of R, I am not able to.
Below is a simplified example (so simple it doesn't show why I want to collapse all the code into a function). First code without any added convenience function.
library(kableExtra)
library(glue)
library(tidyverse)
data <- mtcars[1:10, ] |> select(mpg, cyl, am, carb)
# KableExtra, without added convenience function
kbl(data, booktabs = T, linesep = "", digits = 2,
caption = "Ordinary kbl()") |>
add_header_above(c(" ", "Engine" = 2 , "Other" = 2))
)
Trying to make the same, now with a function where different calls can use different arguments for caption and added headers. The caption part works fine, it's the added headers I'm struggling with.
# Call kableExtra with a function
print_kable <- function(df, caption, header) {
kbl(data, booktabs = T, linesep = "", digits = 2,
# Caption works fine
caption = caption) |>
# I'm unable to develop code that uses a string argument here
add_header_above(c(header)) # 1 col instead of 5
# add_header_above(c({header})) # Same here
# add_header_above(c({{header}})) # and here
# add_header_above(c("{header}")) # and here
# add_header_above(c("{{header}}")) # and here
# add_header_above(c(glue({header}))) # and here
# add_header_above(c(paste(header))) # and here
}
Kable should print with the code below
print_kable(mtcars, caption = "kbl() called with a function",
header = ' " ", "Engine" = 2 , "Other" = 2 ')
Here is a related question:
How to evaluate a glue expression in a nested function?
Placing the function c() in the function call rather than in the function itself works fine. Is this what you're looking for?
print_kable <- function(df, caption, header) {
kbl(data, booktabs = T, linesep = "", digits = 2,
caption = caption) |>
add_header_above(header)
}
print_kable(mtcars, caption = "kbl() called with a function",
header = c(" ", "Engine" = 2 , "Other" = 2))
I am using R version 3.5.2.
I would like to evaluate a string in a kable function, but I am having some issues. Normally, I can pass a string through a for loop using the get function but in the kableExtra::add_header_above function I get the following error:
Error: unexpected '=' in:"print(kable(df4,"html", col.names = c("zero","one")) %>% add_header_above(c(get("string") ="
I have tried a handful of techniques like creating a string outside of the kable function and calling it, using page breaks and print statements in the knit loop and trying the eval function as well. I have also added result ="asis" as suggested here
Here is a reproducible example:
```{r results="asis"}
library("knitr")
library("kableExtra")
df1 <- mtcars %>% dplyr::select(am,vs)
df1a <- df1 %>% mutate(type = "A")
df1b <- df1 %>% mutate(type = "B")
df1c <- df1 %>% mutate(type = "C")
df2 <- rbind(df1a,df1b,df1c)
vector <- as.vector(unique(df2$type))
for (variable in vector) {
df3 <- df2 %>% filter(type == (variable))
df4 <- table(df3$am,df3$vs)
print(kable(df4,"html", col.names = c("zero","one")) %>%
add_header_above(c(get("string") = 3)))
}
```
Ideally, I would like the header of the table to have the string name from the column type. Below is an example of what I want it to look it:
print(kable(df4,"html", col.names = c("zero","one")) %>%
add_header_above(c("A" = 3)))
I understand that the knitr function needs to be treated differently than regular R when using loops as found in this solution but I am still struggling to get the string to be evaluated correctly. Perhaps because the function requires a vecotr input, it is not evalauting it as a string?
You have to define your header as a vector. The name of the header should be the names of the vector and the value of the vector would be the number of columns the header will use.
The loop in the code should look like this:
for (variable in vector) {
df3 <- df2 %>% filter(type == (variable))
df4 <- table(df3$am,df3$vs)
header_temp = 3
names(header_temp) = get("variable")
print(kable(df4,"html", col.names = c("zero","one")) %>%
add_header_above(header_temp))
}
So first I define the number of columns the of the header in the variable header_temp and then i assign a name to it.