Search code examples
rkablekableextrasparklines

kableExtra with spec_plot saved into tex


I am struggling to create a latex table that also has the shape of distributions by group in one of the cells, as sparklines. I tried using kintr::kable and kableExtra packages with the spec_plot function. I create a reproducible example, maybe someone can help. Note that the code also uses dplyr; path is a string, specifying the location on my local hard drive:

This is the sample of the dataset:

dat_plot <- structure(list(year = c(2015, 2016, 2017, 2018, 2019, 2015, 2016, 
2017, 2018, 2019, 2013, 2014, 2015, 2016, 2017), country_name = c("Austria", 
"Austria", "Austria", "Austria", "Austria", "Belgium", "Belgium", 
"Belgium", "Belgium", "Belgium", "Bulgaria", "Bulgaria", "Bulgaria", 
"Bulgaria", "Bulgaria"), no_party_share = c(33.3333333333333, 
33.3333333333333, 42.8571428571429, 50, 50, 75, 75, 75, 75, 75, 
100, 100, 100, 100, 100), no_party_mean = c(20, 20, 20, 20, 20, 
42, 42, 42, 42, 42, 99, 99, 99, 99, 99), no_party_diff = c(50, 
50, 50, 50, 50, 75, 75, 75, 75, 75, 20, 20, 20, 20, 20), nonideo_share = c(16.6666666666667, 
16.6666666666667, 28.5714285714286, 33.3333333333333, 33.3333333333333, 
8.33333333333333, 8.33333333333333, 8.33333333333333, 8.33333333333333, 
8.33333333333333, 100, 100, 100, 100, 90), nonideo_mean = c(26, 
26, 26, 26, 26, 13, 13, 13, 13, 13, 76, 76, 76, 76, 76), nonideo_diff = c(0, 
0, 0, 0, 0, 8, 8, 8, 8, 8, 50, 50, 50, 50, 50), movement_share = c(0, 
0, 0, 0, 0, 16.6666666666667, 16.6666666666667, 16.6666666666667, 
16.6666666666667, 16.6666666666667, 40, 40, 33.3333333333333, 
40, 40), movement_mean = c(0, 0, 0, 0, 0, 13, 13, 13, 13, 13, 
39, 39, 39, 39, 39), movement_diff = c(0, 0, 0, 0, 0, 17, 17, 
17, 17, 17, 0, 0, 0, 0, 0)), class = c("grouped_df", "tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -15L), groups = structure(list(
    country_name = c("Austria", "Belgium", "Bulgaria"), .rows = structure(list(
        1:5, 6:10, 11:15), ptype = integer(0), class = c("vctrs_list_of", 
    "vctrs_vctr", "list"))), row.names = c(NA, -3L), class = c("tbl_df", 
"tbl", "data.frame"), .drop = TRUE))

Here is what I tried:

no_party_list <- split(dat_plot$no_party_share, dat_plot$country_name)
nonideo_list <- split(dat_plot$nonideo_share, dat_plot$country_name)
movement_list <- split(dat_plot$movement_share, dat_plot$country_name)


options(knitr.kable.NA = "")

mod_res <- dat_plot %>%
  select(country_name, no_party_mean, no_party_diff, nonideo_mean, nonideo_diff,
         movement_mean, movement_diff) %>% 
  unique(.) %>% 
  kbl(caption = "Distribution of brands by country \\label{tab:country_table}",
      booktabs = T, format = "latex", col.names = NULL, linesep = "") %>% 
  # kable_styling(latex_options = c("hold_position")) %>% 
  kable_styling(latex_options = "HOLD_position") %>%   # Change will be here
  column_spec(3, image = spec_plot(no_party_list, same_lim = "FALSE")) %>% 
  column_spec(6, image = spec_plot(no_party_list, same_lim = "FALSE")) %>% 
  column_spec(9, image = spec_plot(no_party_list, same_lim = "FALSE")) %>% 
  add_header_above(c(" " = 1, "Mean" = 1, "Diff." = 1, "Shape"=1, 
                              "Mean" = 1, "Diff." = 1, "Shape"=1, 
                              "Mean" = 1, "Diff." = 1, "Shape"=1)) %>%
  add_header_above(c(" " = 1, "No party label" = 3, "Nonideo. ref." = 3, "Movement ref." = 3)) 
  
writeLines(mod_res, paste0(path, "Paper/Tables/", "/desc_countries_table.tex"))

When it creates the tex file the \includegraphics refer to an empty path. The spec_plot somehow does not generate the image files, at least I don't see them locally in the relevant folder. When I try to build it with latex, embedded in a longer latex document where this table should feature, I get a seemingly unrelated error:

Package array Error:  Illegal pream-token (N): `c' used. [\begin{tabular}[t]{lr>{}rrr>{}rrNA>{}NA}]

My guess is that saving the table with writeLines does not properly run the code and does not create local copies of the required images. I also tried save_kable, but I think that's not the way to go. I would appreciate any help on this!


Solution

  • There are several issues with your code.

    • There are only seven columns in the dataframe that you are passing to kbl(), but in column_spec() you are trying to insert image to the Ninth column, which eventually leads to the latex error Package array Error: Illegal pream-token (N)

    • In add_header_above() you are trying to colspan for 10 columns where you have only seven columns.

    • writeLines() will not include the necessary packages in the tex file.

    Therefore at first we need to create placeholder columns to insert the sparkline images. Also we can create a wrapper function around the spec_plot() with the dir and file_type argument specified so that sparkline images are saved as pdfs into a local directory of our working directory.

    options(knitr.kable.NA = "")
    
    # wrapper function ---------------------------------------------------
    spec_plot2 <- function(...) {
      kableExtra::spec_plot(dir = "spec_plots", file_type = "pdf", 
                            same_lim = FALSE, ...)
    }
    # --------------------------------------------------------------------
    
    
    mod_res <- dat_plot %>%
      select(country_name, no_party_mean, no_party_diff, nonideo_mean, nonideo_diff, movement_mean, 
             movement_diff) %>% 
      unique() %>% 
      mutate( no_party_shape = "", nonideo_shape = "", movement_shape = "" ) %>% 
      select(country_name, matches("no_party"), matches("nonideo"), matches("movement")) %>% 
      kbl(
        caption = "Distribution of brands by country \\label{tab:country_table}",
        col.names = c(" ", "Mean", "Diff.", "Shape", "Mean", "Diff.", "Shape", 
                      "Mean", "Diff.", "Shape"),
        booktabs = TRUE, 
        format = "latex", 
        linesep = ""
      ) %>% 
      kable_styling(latex_options = "HOLD_position") %>%   # Change will be here
      column_spec(4, image = spec_plot2(no_party_list)) %>% 
      column_spec(7, image = spec_plot2(nonideo_list)) %>% 
      column_spec(10, image = spec_plot2(movement_list)) %>% 
      add_header_above(c(" " = 1, "No party label" = 3, "Nonideo. ref." = 3, 
                         "Movement ref." = 3))
    
    

    Now the mod_res looks like,

    \begin{table}[H]
    
    \caption{Distribution of brands by country \label{tab:country_table}}
    \centering
    \begin{tabular}[t]{lrr>{}lrr>{}lrr>{}l}
    \toprule
    \multicolumn{1}{c}{ } & \multicolumn{3}{c}{No party label} & \multicolumn{3}{c}{Nonideo. ref.} & \multicolumn{3}{c}{Movement ref.} \\
    \cmidrule(l{3pt}r{3pt}){2-4} \cmidrule(l{3pt}r{3pt}){5-7} \cmidrule(l{3pt}r{3pt}){8-10}
      & Mean & Diff. & Shape & Mean & Diff. & Shape & Mean & Diff. & Shape\\
    \midrule
    Austria & 20 & 50 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_308038534be7.pdf} & 26 & 0 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30806ac26855.pdf} & 0 & 0 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30801a2357ab.pdf}\\
    Belgium & 42 & 75 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30802e60317c.pdf} & 13 & 8 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_308031982ca3.pdf} & 13 & 17 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30802f9657b0.pdf}\\
    Bulgaria & 99 & 20 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30801e4c5ec8.pdf} & 76 & 50 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30806cb263d5.pdf} & 39 & 0 & \includegraphics[width=0.67in, height=0.17in]{file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30805ced3db5.pdf}\\
    \bottomrule
    \end{tabular}
    \end{table}
    

    and the plots are saved as pdfs in the spec_plots directory. Now Then changing the absolute paths of pdfs to a relative ones (that is, changing from file:///C:/Users/User/Desktop/rmd_SO/spec_plots/plot_30805ced3db5.pdf to spec_plots/plot_30805ced3db5.pdf) and copy-pasting the above tex output with the following packages (By skimming over the tex output we can get the idea which packages we need, to compile this tex output)

    \usepackage{booktabs}
    \usepackage{array}
    \usepackage{float}
    \usepackage{graphicx}
    

    in a tex file and compiling the tex file we get the desired table.


    Table with sparkline plot


    main.tex

    \documentclass{article}
    \usepackage{booktabs}
    \usepackage{array}
    \usepackage{float}
    \usepackage{graphicx}
    \begin{document}
    
    
    \begin{table}[H]
    \caption{Distribution of brands by country \label{tab:country_table}}
    \centering
    \begin{tabular}[t]{lrr>{}lrr>{}lrr>{}l}
    \toprule
    \multicolumn{1}{c}{ } & \multicolumn{3}{c}{No party label} & \multicolumn{3}{c}{Nonideo. ref.} & \multicolumn{3}{c}{Movement ref.} \\
    \cmidrule(l{3pt}r{3pt}){2-4} \cmidrule(l{3pt}r{3pt}){5-7} \cmidrule(l{3pt}r{3pt}){8-10}
      & Mean & Diff. & Shape & Mean & Diff. & Shape & Mean & Diff. & Shape\\
    \midrule
    Austria & 20 & 50 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_308038534be7.pdf} & 26 & 0 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30806ac26855.pdf} & 0 & 0 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30801a2357ab.pdf}\\
    Belgium & 42 & 75 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30802e60317c.pdf} & 13 & 8 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_308031982ca3.pdf} & 13 & 17 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30802f9657b0.pdf}\\
    Bulgaria & 99 & 20 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30801e4c5ec8.pdf} & 76 & 50 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30806cb263d5.pdf} & 39 & 0 & \includegraphics[width=0.33in, height=0.17in]{spec_plots/plot_30805ced3db5.pdf}\\
    \bottomrule
    \end{tabular}
    \end{table}
    \end{document}
    

    (Note that, I have changed the width and height in the \includegraphics a bit)


    Now if we want, we can also get the tex output in a tex file using save_kable() with the keep_tex argument set as TRUE

    mod_res %>% 
      save_kable("mod_res.pdf", keep_tex = TRUE) 
    

    This will generate the mod_res.tex file in our working directory with an exclusive list of latex packages (some of which aren't specifically needed for compiling this table).

    Though this save_kable command is not generating the mod_res.pdf file in my windows machine due to a latex compilation error (which is due to the fact that plot-pdfs paths are appended with file:///), We are getting the mod_res.tex file which we want actually.