Hao Zhu | 3166f06 | 2017-06-26 07:51:46 -1000 | [diff] [blame] | 1 | #' Collapse repeated rows to multirow cell |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 2 | #' |
Hao Zhu | 8a160b1 | 2017-06-26 13:41:35 -1000 | [diff] [blame] | 3 | #' @description Collapse same values in columns into multirow cells. This |
| 4 | #' feature does similar things with `group_rows`. However, unlike `group_rows`, |
| 5 | #' it analyzes existing columns, finds out rows that can be grouped together, |
| 6 | #' and make them multirow cells. Note that if you want to use `column_spec` to |
| 7 | #' specify column styles, you should use `column_spec` before `collapse_rows`. |
| 8 | #' |
| 9 | #' @param kable_input Output of `knitr::kable()` with `format` specified |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 10 | #' @param columns A numeric value or vector indicating in which column(s) rows |
Jakob Richter | aebd829 | 2018-10-31 16:27:29 +0100 | [diff] [blame] | 11 | #' need to be collapsed. |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 12 | #' @param valign Select from "top", "middle" (default), "bottom". The reason why |
Hao Zhu | ec16936 | 2018-05-21 01:05:29 -0400 | [diff] [blame] | 13 | #' "top" is not default is that the multirow package on CRAN win-builder is |
| 14 | #' not up to date. |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 15 | #' Only used when `row_group_label_position` is `identity`. |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 16 | #' @param latex_hline Option controlling the behavior of adding hlines to table. |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 17 | #' Choose from `full`, `major`, `none`, `custom` and `linespace`. |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 18 | #' @param custom_latex_hline Numeric column positions whose collapsed rows will |
| 19 | #' be separated by hlines. |
| 20 | #' @param row_group_label_position Option controlling positions of row group |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 21 | #' labels. Choose from `identity`, `stack`, or `first` -- the latter behaves |
| 22 | #' like `identity` when `row_group_label_position` is `top` but without using |
| 23 | #' the multirow package. |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 24 | #' @param row_group_label_fonts A list of arguments that can be supplied to |
| 25 | #' group_rows function to format the row group label when |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 26 | #' `row_group_label_position` is `stack`. |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 27 | #' @param headers_to_remove Numeric column positions where headers should be |
| 28 | #' removed when they are stacked. |
Hao Zhu | c985894 | 2020-08-11 21:41:09 -0400 | [diff] [blame] | 29 | #' @param target If multiple columns are selected to do collapsing and a target |
| 30 | #' column is specified, this target column will be used to collapse other |
| 31 | #' columns based on the groups of this target column. |
| 32 | #' @param col_names T/F. A LaTeX specific option. If you set `col.names` be |
| 33 | #' `NULL` in your `kable` call, you need to set this option false to let |
| 34 | #' everything work properly. |
Hao Zhu | 71b30f2 | 2020-08-12 22:47:13 -0400 | [diff] [blame] | 35 | #' @param longtable_clean_cut T/F with default T. Multirow cell sometimes are |
| 36 | #' displayed incorrectly around pagebreak. This option forces groups to cut |
| 37 | #' before the end of a page. If you have a group that is longer than 1 page, |
| 38 | #' you need to turn off this option. |
Hao Zhu | 8a160b1 | 2017-06-26 13:41:35 -1000 | [diff] [blame] | 39 | #' |
Hao Zhu | 9399dcc | 2020-08-26 17:27:38 -0400 | [diff] [blame] | 40 | #' @examples |
| 41 | #' \dontrun{ |
| 42 | #' dt <- data.frame(a = c(1, 1, 2, 2), b = c("a", "a", "a", "b")) |
Hao Zhu | 5a7689e | 2017-06-26 15:37:24 -1000 | [diff] [blame] | 43 | #' x <- knitr::kable(dt, "html") |
| 44 | #' collapse_rows(x) |
Hao Zhu | 9399dcc | 2020-08-26 17:27:38 -0400 | [diff] [blame] | 45 | #' } |
Hao Zhu | 5a7689e | 2017-06-26 15:37:24 -1000 | [diff] [blame] | 46 | #' |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 47 | #' @export |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 48 | collapse_rows <- function(kable_input, columns = NULL, |
Hao Zhu | ec16936 | 2018-05-21 01:05:29 -0400 | [diff] [blame] | 49 | valign = c("middle", "top", "bottom"), |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 50 | latex_hline = c("full", "major", "none", "custom", |
| 51 | "linespace"), |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 52 | row_group_label_position = c("identity", "stack", "first"), |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 53 | custom_latex_hline = NULL, |
| 54 | row_group_label_fonts = NULL, |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 55 | headers_to_remove = NULL, |
Hao Zhu | c985894 | 2020-08-11 21:41:09 -0400 | [diff] [blame] | 56 | target = NULL, |
Hao Zhu | 71b30f2 | 2020-08-12 22:47:13 -0400 | [diff] [blame] | 57 | col_names = TRUE, |
| 58 | longtable_clean_cut = TRUE) { |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 59 | kable_format <- attr(kable_input, "format") |
| 60 | if (!kable_format %in% c("html", "latex")) { |
Hao Zhu | 401ebd8 | 2018-01-14 17:10:20 -0500 | [diff] [blame] | 61 | warning("Please specify format in kable. kableExtra can customize either ", |
| 62 | "HTML or LaTeX outputs. See https://haozhu233.github.io/kableExtra/ ", |
| 63 | "for details.") |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 64 | return(kable_input) |
| 65 | } |
Hao Zhu | 33b865f | 2020-08-18 02:10:43 -0400 | [diff] [blame] | 66 | valign <- match.arg(valign) |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 67 | if (!is.null(target)) { |
| 68 | if (length(target) > 1 && is.integer(target)) { |
| 69 | stop("target can only be a length 1 integer") |
| 70 | } |
| 71 | } |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 72 | if (kable_format == "html") { |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 73 | return(collapse_rows_html(kable_input, columns, valign, target)) |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 74 | } |
| 75 | if (kable_format == "latex") { |
Hao Zhu | 33b865f | 2020-08-18 02:10:43 -0400 | [diff] [blame] | 76 | latex_hline <- match.arg(latex_hline) |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 77 | row_group_label_position <- match.arg(row_group_label_position, |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 78 | c("identity", "stack", "first")) |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 79 | return(collapse_rows_latex(kable_input, columns, latex_hline, valign, |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 80 | row_group_label_position, row_group_label_fonts, custom_latex_hline, |
Hao Zhu | 71b30f2 | 2020-08-12 22:47:13 -0400 | [diff] [blame] | 81 | headers_to_remove, target, col_names, longtable_clean_cut)) |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 82 | } |
| 83 | } |
| 84 | |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 85 | collapse_rows_html <- function(kable_input, columns, valign, target) { |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 86 | kable_attrs <- attributes(kable_input) |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 87 | kable_xml <- kable_as_xml(kable_input) |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 88 | kable_tbody <- xml_tpart(kable_xml, "tbody") |
| 89 | |
| 90 | kable_dt <- rvest::html_table(xml2::read_html(as.character(kable_input)))[[1]] |
Hao Zhu | a6af5c0 | 2021-03-11 22:28:25 -0500 | [diff] [blame] | 91 | kable_dt <- as.data.frame(kable_dt) |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 92 | if (is.null(columns)) { |
| 93 | columns <- seq(1, ncol(kable_dt)) |
| 94 | } |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 95 | if (!is.null(target)) { |
| 96 | if (!target %in% columns) { |
| 97 | stop("target has to be within the range of columns") |
| 98 | } |
| 99 | } |
Hao Zhu | 2345676 | 2018-03-26 12:30:10 -0400 | [diff] [blame] | 100 | if (!is.null(kable_attrs$header_above)) { |
| 101 | kable_dt_col_names <- unlist(kable_dt[kable_attrs$header_above, ]) |
| 102 | kable_dt <- kable_dt[-(1:kable_attrs$header_above),] |
| 103 | names(kable_dt) <- kable_dt_col_names |
| 104 | } |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 105 | collapse_matrix <- collapse_row_matrix(kable_dt, columns, target = target) |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 106 | |
| 107 | for (i in 1:nrow(collapse_matrix)) { |
| 108 | matrix_row <- collapse_matrix[i, ] |
Hao Zhu | 38cdcdb | 2017-06-27 09:08:30 -1000 | [diff] [blame] | 109 | names(matrix_row) <- names(collapse_matrix) |
Hao Zhu | 3166f06 | 2017-06-26 07:51:46 -1000 | [diff] [blame] | 110 | target_row <- xml_child(kable_tbody, i) |
| 111 | row_node_rm_count <- 0 |
| 112 | for (j in 1:length(matrix_row)) { |
| 113 | collapsing_col <- as.numeric(sub("x", "", names(matrix_row)[j])) - |
| 114 | row_node_rm_count |
| 115 | target_cell <- xml_child(target_row, collapsing_col) |
| 116 | if (matrix_row[j] == 0) { |
| 117 | xml_remove(target_cell) |
| 118 | row_node_rm_count <- row_node_rm_count + 1 |
| 119 | } else if (matrix_row[j] != 1) { |
| 120 | xml_attr(target_cell, "rowspan") <- matrix_row[j] |
| 121 | xml_attr(target_cell, "style") <- paste0( |
| 122 | xml_attr(target_cell, "style"), |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 123 | "vertical-align: ", valign, " !important;") |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 124 | } |
| 125 | } |
| 126 | } |
| 127 | |
Hao Zhu | f2dfd14 | 2017-07-24 14:43:28 -0400 | [diff] [blame] | 128 | out <- as_kable_xml(kable_xml) |
Hao Zhu | fdff6f4 | 2020-08-09 14:38:10 -0400 | [diff] [blame] | 129 | kable_attrs$collapse_matrix <- collapse_matrix |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 130 | attributes(out) <- kable_attrs |
Hao Zhu | f210083 | 2018-01-11 16:20:29 -0500 | [diff] [blame] | 131 | if (!"kableExtra" %in% class(out)) class(out) <- c("kableExtra", class(out)) |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 132 | return(out) |
| 133 | } |
| 134 | |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 135 | split_factor <- function(x) { |
| 136 | group_idx <- seq(1, length(x)) |
| 137 | return(factor(unlist(lapply(group_idx, function(i) {rep(i, x[i])})))) |
| 138 | } |
| 139 | |
| 140 | collapse_row_matrix <- function(kable_dt, columns, html = T, target = NULL) { |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 141 | if (html) { |
| 142 | column_block <- function(x) c(x, rep(0, x - 1)) |
| 143 | } else { |
| 144 | column_block <- function(x) c(rep(0, x - 1), x) |
| 145 | } |
| 146 | mapping_matrix <- list() |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 147 | if (is.null(target)) { |
| 148 | for (i in columns) { |
| 149 | mapping_matrix[[paste0("x", i)]] <- unlist(lapply( |
| 150 | rle(kable_dt[, i])$lengths, column_block)) |
| 151 | } |
| 152 | } else { |
| 153 | target_group = split_factor(rle(kable_dt[, target])$lengths) |
| 154 | for (i in columns) { |
| 155 | column_split = split(kable_dt[, i], target_group) |
| 156 | mapping_matrix[[paste0("x", i)]] <- unlist(lapply( |
| 157 | column_split, function(sp) { |
| 158 | lapply(rle(sp)$length, column_block) |
| 159 | })) |
| 160 | } |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 161 | } |
Hao Zhu | 5e2528e | 2020-08-03 09:16:34 -0400 | [diff] [blame] | 162 | |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 163 | mapping_matrix <- data.frame(mapping_matrix) |
| 164 | return(mapping_matrix) |
| 165 | } |
| 166 | |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 167 | collapse_rows_latex <- function(kable_input, columns, latex_hline, valign, |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 168 | row_group_label_position, row_group_label_fonts, |
Hao Zhu | c985894 | 2020-08-11 21:41:09 -0400 | [diff] [blame] | 169 | custom_latex_hline, headers_to_remove, target, |
Hao Zhu | 71b30f2 | 2020-08-12 22:47:13 -0400 | [diff] [blame] | 170 | col_names, longtable_clean_cut) { |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 171 | table_info <- magic_mirror(kable_input) |
Hao Zhu | c33b1f4 | 2020-10-05 23:16:57 -0400 | [diff] [blame] | 172 | if (table_info$nrow <= 2) return(kable_input) |
Hao Zhu | 3fc0e88 | 2018-04-03 16:06:41 -0400 | [diff] [blame] | 173 | out <- solve_enc(kable_input) |
Hao Zhu | 876bcb0 | 2020-08-12 23:02:51 -0400 | [diff] [blame] | 174 | out <- gsub("\\\\addlinespace\n", "", out) |
Hao Zhu | 064990d | 2017-10-17 18:08:42 -0400 | [diff] [blame] | 175 | |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 176 | valign <- switch( |
| 177 | valign, |
| 178 | top = "\\[t\\]", |
| 179 | middle = "", |
| 180 | bottom = "\\[b\\]" |
| 181 | ) |
| 182 | |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 183 | if (is.null(columns)) { |
| 184 | columns <- seq(1, table_info$ncol) |
| 185 | } |
Hao Zhu | 064990d | 2017-10-17 18:08:42 -0400 | [diff] [blame] | 186 | |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 187 | contents <- table_info$contents |
Hao Zhu | c985894 | 2020-08-11 21:41:09 -0400 | [diff] [blame] | 188 | kable_dt <- kable_dt_latex(contents, col_names) |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 189 | |
Hao Zhu | aa42441 | 2020-08-03 09:21:46 -0400 | [diff] [blame] | 190 | collapse_matrix_rev <- collapse_row_matrix(kable_dt, columns, html = TRUE, |
| 191 | target) |
| 192 | collapse_matrix <- collapse_row_matrix(kable_dt, columns, html = FALSE, |
| 193 | target) |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 194 | |
| 195 | new_kable_dt <- kable_dt |
Jakob Richter | aebd829 | 2018-10-31 16:27:29 +0100 | [diff] [blame] | 196 | for (j in seq_along(columns)) { |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 197 | column_align <- table_info$align_vector_origin[columns[j]] |
| 198 | column_width <- ifelse( |
| 199 | is.null(table_info$column_width[[paste0("column_", columns[j])]]), |
Hao Zhu | 4e34cd8 | 2020-08-19 01:54:23 -0400 | [diff] [blame] | 200 | "\\*", table_info$column_width[paste0("column_", columns[j])]) |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 201 | for (i in seq(1:nrow(collapse_matrix))) { |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 202 | if(row_group_label_position == 'stack'){ |
Jakob Richter | aebd829 | 2018-10-31 16:27:29 +0100 | [diff] [blame] | 203 | if(columns[j] < ncol(collapse_matrix) || collapse_matrix_rev[i, j] == 0){ |
| 204 | new_kable_dt[i, columns[j]] <- '' |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 205 | } |
Kirill Müller | 64938ec | 2022-05-26 20:08:56 +0200 | [diff] [blame] | 206 | } else if(row_group_label_position == 'first'){ |
| 207 | if(columns[j] <= ncol(collapse_matrix) && collapse_matrix_rev[i, j] == 0){ |
| 208 | new_kable_dt[i, columns[j]] <- '' |
| 209 | } |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 210 | } else { |
Jakob Richter | aebd829 | 2018-10-31 16:27:29 +0100 | [diff] [blame] | 211 | new_kable_dt[i, columns[j]] <- collapse_new_dt_item( |
| 212 | kable_dt[i, columns[j]], collapse_matrix[i, j], column_width, |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 213 | align = column_align, valign = valign |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 214 | ) |
| 215 | } |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 216 | } |
| 217 | } |
Hao Zhu | 654c91f | 2017-07-03 14:03:34 -0400 | [diff] [blame] | 218 | |
| 219 | midrule_matrix <- collapse_row_matrix(kable_dt, seq(1, table_info$ncol), |
Hao Zhu | aa42441 | 2020-08-03 09:21:46 -0400 | [diff] [blame] | 220 | html = FALSE, target) |
Hao Zhu | 654c91f | 2017-07-03 14:03:34 -0400 | [diff] [blame] | 221 | midrule_matrix[setdiff(seq(1, table_info$ncol), columns)] <- 1 |
| 222 | |
| 223 | ex_bottom <- length(contents) - 1 |
| 224 | contents[2:ex_bottom] <- paste0(contents[2:ex_bottom], "\\\\\\\\") |
| 225 | if (!table_info$booktabs) { |
| 226 | contents[2:ex_bottom] <- paste0(contents[2:ex_bottom], "\n\\\\hline") |
| 227 | } |
Hao Zhu | 01b15b8 | 2018-01-12 17:48:21 -0500 | [diff] [blame] | 228 | |
| 229 | new_contents <- c() |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 230 | if(row_group_label_position == 'stack'){ |
| 231 | if(is.null(headers_to_remove)) headers_to_remove <- head(columns, -1) |
| 232 | table_info$colnames[headers_to_remove] <- '' |
| 233 | new_header <- paste(table_info$colnames, collapse = ' & ') |
| 234 | out <- sub(contents[1], new_header, out) |
| 235 | table_info$contents[1] <- new_header |
| 236 | } |
| 237 | if(latex_hline == 'custom' & is.null(custom_latex_hline)){ |
| 238 | if(row_group_label_position == 'stack'){ |
| 239 | custom_latex_hline = 1:2 |
| 240 | } else { |
| 241 | custom_latex_hline = 1 |
| 242 | } |
| 243 | } |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 244 | |
| 245 | if (table_info$tabular == "longtable" & longtable_clean_cut) { |
| 246 | if (max(collapse_matrix) > 50) { |
| 247 | warning("It seems that you have a group larger than 50 rows and span ", |
| 248 | "over a page. You probably want to set longtable_clean_cut to ", |
| 249 | "be FALSE.") |
| 250 | } |
| 251 | pagebreak_hint <- "\\\\pagebreak[0]" |
| 252 | nopagebreak <- "\\\\nopagebreak" |
| 253 | # out <- gsub("\\\\\\\\($|\n)", "\\\\\\\\\\\\nopagebreak\\1", out) |
| 254 | # out <- gsub("(\\\\cmidrule[{][^}]*[}])", "\\1\\\\pagebreak[0]", out) |
| 255 | } else { |
| 256 | pagebreak_hint <- "" |
| 257 | nopagebreak <- "" |
| 258 | } |
| 259 | |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 260 | for (i in seq(1:nrow(collapse_matrix))) { |
| 261 | new_contents[i] <- paste0(new_kable_dt[i, ], collapse = " & ") |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 262 | table_info$contents[i + 1] <- new_contents[i] |
Hao Zhu | 654c91f | 2017-07-03 14:03:34 -0400 | [diff] [blame] | 263 | if (i != nrow(collapse_matrix)) { |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 264 | row_midrule <- switch( |
| 265 | latex_hline, |
| 266 | "none" = "", |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 267 | "full" = paste0( |
Hao Zhu | 71b30f2 | 2020-08-12 22:47:13 -0400 | [diff] [blame] | 268 | midline_groups(which(as.numeric(midrule_matrix[i, ]) > 0), |
| 269 | table_info$booktabs), |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 270 | ifelse( |
| 271 | sum(as.numeric(midrule_matrix[i, ]) > 0) == ncol(midrule_matrix), |
| 272 | pagebreak_hint, nopagebreak |
| 273 | ) |
Hao Zhu | 71b30f2 | 2020-08-12 22:47:13 -0400 | [diff] [blame] | 274 | ), |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 275 | "major" = ifelse( |
| 276 | sum(as.numeric(midrule_matrix[i, ]) > 0) == ncol(midrule_matrix), |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 277 | paste0(midline_groups(which(as.numeric(midrule_matrix[i, ]) > 0), |
| 278 | table_info$booktabs), pagebreak_hint), |
| 279 | nopagebreak |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 280 | ), |
| 281 | "custom" = ifelse( |
| 282 | sum(as.numeric(midrule_matrix[i, custom_latex_hline])) > 0, |
| 283 | midline_groups(which(as.numeric(midrule_matrix[i, ]) > 0), |
| 284 | table_info$booktabs), |
| 285 | "" |
Hao Zhu | 876bcb0 | 2020-08-12 23:02:51 -0400 | [diff] [blame] | 286 | ), |
| 287 | "linespace"= ifelse( |
| 288 | sum(as.numeric(midrule_matrix[i, ]) > 0) == ncol(midrule_matrix), |
Hao Zhu | d0f7c8a | 2020-08-20 01:17:23 -0400 | [diff] [blame] | 289 | "\\\\addlinespace", |
Hao Zhu | 876bcb0 | 2020-08-12 23:02:51 -0400 | [diff] [blame] | 290 | "" |
| 291 | ) |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 292 | ) |
Hao Zhu | 654c91f | 2017-07-03 14:03:34 -0400 | [diff] [blame] | 293 | new_contents[i] <- paste0(new_contents[i], "\\\\\\\\\n", row_midrule) |
| 294 | } |
Hao Zhu | b3f26ae | 2020-08-04 00:36:52 -0400 | [diff] [blame] | 295 | out <- sub(contents[i + 1], new_contents[i], out, perl=TRUE) |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 296 | } |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 297 | out <- structure(out, format = "latex", class = "knitr_kable") |
| 298 | table_info$collapse_rows <- TRUE |
Hao Zhu | ebdb3c2 | 2020-08-12 08:27:38 -0400 | [diff] [blame] | 299 | table_info$collapse_matrix <- collapse_matrix |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 300 | attr(out, "kable_meta") <- table_info |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 301 | if(row_group_label_position == 'stack'){ |
| 302 | group_row_index_list <- collapse_rows_index(kable_dt, head(columns, -1)) |
| 303 | out <- collapse_rows_latex_stack(out, group_row_index_list, row_group_label_fonts) |
| 304 | } |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 305 | return(out) |
| 306 | } |
| 307 | |
Hao Zhu | c985894 | 2020-08-11 21:41:09 -0400 | [diff] [blame] | 308 | kable_dt_latex <- function(x, col_names) { |
| 309 | if (col_names) { |
| 310 | x <- x[-1] |
| 311 | } |
| 312 | data.frame(do.call(rbind, str_split(x, " & ")), stringsAsFactors = FALSE) |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 313 | } |
| 314 | |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 315 | collapse_new_dt_item <- function(x, span, width = NULL, align, valign) { |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 316 | if (span == 0) return("") |
| 317 | if (span == 1) return(x) |
| 318 | out <- paste0( |
Hao Zhu | 5dd3e28 | 2018-05-20 18:39:48 -0400 | [diff] [blame] | 319 | "\\\\multirow", valign, "\\{", -span, "\\}\\{", |
Hao Zhu | f4b3529 | 2017-06-25 22:38:37 -1000 | [diff] [blame] | 320 | ifelse(is.null(width), "\\*", width), |
| 321 | "\\}\\{", |
| 322 | switch(align, |
| 323 | "l" = "\\\\raggedright\\\\arraybackslash ", |
| 324 | "c" = "\\\\centering\\\\arraybackslash ", |
| 325 | "r" = "\\\\raggedleft\\\\arraybackslash "), |
| 326 | x, "\\}" |
| 327 | ) |
| 328 | return(out) |
Hao Zhu | 2a87e8e | 2017-06-14 15:49:33 -0400 | [diff] [blame] | 329 | } |
Hao Zhu | 654c91f | 2017-07-03 14:03:34 -0400 | [diff] [blame] | 330 | |
| 331 | midline_groups <- function(x, booktabs = T) { |
| 332 | diffs <- c(1, diff(x)) |
| 333 | start_indexes <- c(1, which(diffs > 1)) |
Hao Zhu | 12b0ade | 2018-01-13 16:19:58 -0500 | [diff] [blame] | 334 | end_indexes <- c(start_indexes - 1, length(x)) |
Hao Zhu | 654c91f | 2017-07-03 14:03:34 -0400 | [diff] [blame] | 335 | ranges <- paste0(x[start_indexes], "-", x[end_indexes]) |
| 336 | if (booktabs) { |
| 337 | out <- paste0("\\\\cmidrule{", ranges, "}") |
| 338 | } else { |
| 339 | out <- paste0("\\\\cline{", ranges, "}") |
| 340 | } |
| 341 | out <- paste0(out, collapse = "\n") |
| 342 | return(out) |
| 343 | } |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 344 | |
Hao Zhu | 876bcb0 | 2020-08-12 23:02:51 -0400 | [diff] [blame] | 345 | linespace_groups <- function(x) { |
| 346 | diffs <- c(1, diff(x)) |
| 347 | start_indexes <- c(1, which(diffs > 1)) |
| 348 | end_indexes <- c(start_indexes - 1, length(x)) |
| 349 | ranges <- paste0(x[start_indexes], "-", x[end_indexes]) |
| 350 | out <- paste0("\\\\addlinespace") |
| 351 | out <- paste0(out, collapse = "\n") |
| 352 | return(out) |
| 353 | } |
| 354 | |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 355 | |
| 356 | collapse_rows_index <- function(kable_dt, columns) { |
| 357 | format_to_row_index <- function(x){ |
| 358 | x = rle(x) |
| 359 | out = x$lengths |
| 360 | names(out) = x$values |
| 361 | out |
| 362 | } |
| 363 | group_rows_index_list <- lapply(columns, function(x) { |
| 364 | format_to_row_index(kable_dt[, x]) |
| 365 | }) |
| 366 | return(group_rows_index_list) |
| 367 | } |
| 368 | |
| 369 | |
| 370 | collapse_rows_latex_stack <- function(kable_input, group_row_index_list, |
| 371 | row_group_label_fonts){ |
| 372 | merge_lists <- function(default_list, updated_list){ |
| 373 | for(x in names(updated_list)){ |
| 374 | default_list[[x]] <- updated_list[[x]] |
| 375 | } |
| 376 | return(default_list) |
| 377 | } |
| 378 | default_font_list <- list( |
| 379 | list(bold = T, italic = F), |
| 380 | list(bold = F, italic = T), |
| 381 | list(bold = F, italic = F) |
| 382 | ) |
| 383 | n_default_fonts = length(default_font_list) |
| 384 | n_supplied_fonts = length(row_group_label_fonts) |
| 385 | group_row_font_list <- list() |
| 386 | out <- kable_input |
| 387 | for(i in 1:length(group_row_index_list)){ |
| 388 | if(i > n_default_fonts){ |
| 389 | group_row_args <- default_font_list[[n_default_fonts]] |
| 390 | } else { |
| 391 | group_row_args <- default_font_list[[i]] |
| 392 | } |
| 393 | if(i <= n_supplied_fonts){ |
| 394 | group_row_args <- merge_lists(group_row_args, row_group_label_fonts[[i]]) |
| 395 | } |
| 396 | group_row_args <- merge_lists( |
Hao Zhu | 718fa3f | 2020-08-19 08:23:28 -0400 | [diff] [blame] | 397 | list(kable_input = out, index = group_row_index_list[[i]], escape = FALSE), |
georgegui | eaeb0cd | 2018-03-30 17:39:46 -0500 | [diff] [blame] | 398 | group_row_args) |
| 399 | out <- do.call(group_rows, group_row_args) |
| 400 | } |
| 401 | return(out) |
| 402 | } |