-
Notifications
You must be signed in to change notification settings - Fork 196
/
gt_split.R
238 lines (200 loc) · 6.44 KB
/
gt_split.R
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#------------------------------------------------------------------------------#
#
# /$$
# | $$
# /$$$$$$ /$$$$$$
# /$$__ $$|_ $$_/
# | $$ \ $$ | $$
# | $$ | $$ | $$ /$$
# | $$$$$$$ | $$$$/
# \____ $$ \___/
# /$$ \ $$
# | $$$$$$/
# \______/
#
# This file is part of the 'rstudio/gt' project.
#
# Copyright (c) 2018-2024 gt authors
#
# For full copyright and license information, please look at
# https://gt.rstudio.com/LICENSE.html
#
#------------------------------------------------------------------------------#
#' Split a table into a group of tables (a `gt_group`)
#'
#' @description
#'
#' With a **gt** table, you can split it into multiple tables and get that
#' collection in a `gt_group` object. This function is useful for those cases
#' where you want to section up a table in a specific way and print those
#' smaller tables across multiple pages (in RTF and Word outputs, primarily via
#' [gtsave()]), or, with breaks between them when the output context is HTML.
#'
#' @inheritParams fmt_number
#'
#' @param row_every_n *Split at every n rows*
#'
#' `scalar<numeric|integer>` // *default:* `NULL` (`optional`)
#'
#' A directive to split at every *n* number of rows. This argument expects a
#' single numerical value.
#'
#' @param row_slice_i *Row-slicing indices*
#'
#' `vector<numeric|integer>` // *default:* `NULL` (`optional`)
#'
#' An argument for splitting at specific row indices. Here, we expect either a
#' vector of index values or a function that evaluates to a numeric vector.
#'
#' @param col_slice_at *Column-slicing locations*
#'
#' `<column-targeting expression>` // *default:* `NULL` (`optional`)
#'
#' Any columns where vertical splitting across should occur. The splits occur
#' to the right of the resolved column names. Can either be a series of column
#' names provided in [c()], a vector of column indices, or a select helper
#' function. Examples of select helper functions include [starts_with()],
#' [ends_with()], [contains()], [matches()], [one_of()], [num_range()], and
#' [everything()].
#'
#' @return An object of class `gt_group`.
#'
#' @section Examples:
#'
#' Use a subset of the [`gtcars`] dataset to create a **gt** table. Format the
#' `msrp` column to display numbers as currency values, set column widths with
#' [cols_width()], and split the table at every five rows with `gt_split()`.
#' This creates a `gt_group` object containing two tables. Printing this object
#' yields two tables separated by a line break.
#'
#' ```r
#' gtcars |>
#' dplyr::slice_head(n = 10) |>
#' dplyr::select(mfr, model, year, msrp) |>
#' gt() |>
#' fmt_currency(columns = msrp) |>
#' cols_width(
#' year ~ px(80),
#' everything() ~ px(150)
#' ) |>
#' gt_split(row_every_n = 5)
#' ```
#'
#' \if{html}{\out{
#' `r man_get_image_tag(file = "man_gt_split_1.png")`
#' }}
#'
#' Use a smaller subset of the [`gtcars`] dataset to create a **gt** table.
#' Format the `msrp` column to display numbers as currency values, set the table
#' width with [tab_options()] and split the table at the `model` column This
#' creates a `gt_group` object again containing two tables but this time we get
#' a vertical split. Printing this object yields two tables of the same width.
#'
#' ```r
#' gtcars |>
#' dplyr::slice_head(n = 5) |>
#' dplyr::select(mfr, model, year, msrp) |>
#' gt() |>
#' fmt_currency(columns = msrp) |>
#' tab_options(table.width = px(400)) |>
#' gt_split(col_slice_at = "model")
#' ```
#'
#' \if{html}{\out{
#' `r man_get_image_tag(file = "man_gt_split_2.png")`
#' }}
#'
#' @family table group functions
#' @section Function ID:
#' 14-2
#'
#' @section Function Introduced:
#' `v0.9.0` (Mar 31, 2023)
#'
#' @export
gt_split <- function(
data,
row_every_n = NULL,
row_slice_i = NULL,
col_slice_at = NULL
) {
# Perform input object validation
stop_if_not_gt_tbl(data = data)
# Resolution of columns as character vectors
col_slice_at <-
resolve_cols_c(
expr = {{ col_slice_at }},
data = data,
null_means = "nothing"
)
gt_tbl_built <- build_data(data = data, context = "html")
# Get row count for table (data rows)
n_rows_data <- nrow(gt_tbl_built[["_stub_df"]])
row_slice_vec <- rep(1L, n_rows_data)
row_every_n_idx <- c()
if (!is.null(row_every_n)) {
row_every_n_idx <- seq_len(n_rows_data)[seq(0, n_rows_data, row_every_n)]
}
row_slice_i_idx <- c()
if (!is.null(row_slice_i)) {
row_slice_i_idx <- row_slice_i
}
row_idx <- sort(unique(c(row_every_n_idx, row_slice_i_idx)))
group_i <- 0L
for (i in seq_along(row_slice_vec)) {
if (i %in% (row_idx + 1)) {
group_i <- group_i + 1L
}
row_slice_vec[i] <- row_slice_vec[i] + group_i
}
row_range_list <-
split(
seq_len(n_rows_data),
row_slice_vec
)
gt_tbl_main <- data
gt_group <- gt_group(.use_grp_opts = FALSE)
for (i in seq_along(row_range_list)) {
gt_tbl_i <- gt_tbl_main
gt_tbl_i[["_data"]] <- gt_tbl_i[["_data"]][row_range_list[[i]], ]
gt_tbl_i[["_stub_df"]] <- gt_tbl_i[["_stub_df"]][row_range_list[[i]], ]
if (!is.null(col_slice_at)) {
# Get all visible vars in their finalized order
visible_col_vars <- dt_boxhead_get_vars_default(data = data)
# Stop function if any of the columns to split at aren't visible columns
if (!all(col_slice_at %in% visible_col_vars)) {
cli::cli_abort(
"All values provided in `col_slice_at` must correspond to visible columns."
)
}
# Obtain all of the column indices for vertical splitting
col_idx <- which(visible_col_vars %in% col_slice_at)
col_slice_vec <- rep(1L, length(visible_col_vars))
group_j <- 0L
for (i in seq_along(col_slice_vec)) {
if (i %in% (col_idx + 1)) {
group_j <- group_j + 1L
}
col_slice_vec[i] <- col_slice_vec[i] + group_j
}
col_range_list <-
split(
seq_along(visible_col_vars),
col_slice_vec
)
for (j in seq_along(col_range_list)) {
gt_tbl_j <- gt_tbl_i
gt_tbl_j[["_data"]] <-
gt_tbl_j[["_data"]][, visible_col_vars[col_range_list[[j]]]]
gt_tbl_j[["_boxhead"]] <-
gt_tbl_j[["_boxhead"]][
gt_tbl_j[["_boxhead"]]$var %in% visible_col_vars[col_range_list[[j]]],
]
gt_group <- grp_add(gt_group, gt_tbl_j)
}
} else {
gt_group <- grp_add(gt_group, gt_tbl_i)
}
}
gt_group
}