Skip to content

Commit 1e9ef2b

Browse files
authored
Allow an empty table with zero rows to create an empty gt table (#283)
* Modify linebreaks * Transform all tbl contents to NA if there are rows * Ensure that all table cells are `NA_character_` * Use functions that check if row count is > 0 * Only execute statement if number of rows >0 * Remove a line break * Create a dummy row for an zero-row table * Return NULL if output_df has a zero row count * If tbl formally has no rows, assign it a single row * Modify a conditional statement * Move util functions to a separate file * Add roxygen documentation for two util fcns * Create zero-row `rows_df` if `data_tbl` is empty * Account for grouped cols when making `final_df` * Add several testthat tests * Stop function if table has zero columns * Create `boxh_df` from matrix object * Use `seq_len()` instead of `seq()` * Add missing closing bracket * Make code review changes
1 parent a47c023 commit 1e9ef2b

File tree

7 files changed

+150
-40
lines changed

7 files changed

+150
-40
lines changed

R/build_data.R

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -166,15 +166,11 @@ build_data <- function(data, context) {
166166

167167
# Replace NA values in the `groupname` column if there is a reserved
168168
# label for the unlabeled group
169-
groups_df[is.na(groups_df[, "groupname"]), "groupname"] <- others_group
169+
groups_df <- replace_na_groups_df(groups_df, others_group)
170170

171171
# Replace NA values in the `group` and `group_label` columns of
172172
# `group_rows_df`
173-
if (!is.na(others_group)) {
174-
groups_rows_df[
175-
is.na(groups_rows_df[, "group"]),
176-
c("group", "group_label")] <- others_group
177-
}
173+
groups_rows_df <- replace_na_groups_rows_df(groups_rows_df, others_group)
178174

179175
data_attr$boxh_df <- boxh_df
180176
data_attr$stub_df <- stub_df

R/gt.R

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,17 @@ gt <- function(data,
7676
id = random_id(),
7777
stub_group.sep = getOption("gt.stub_group.sep", " - ")) {
7878

79+
# Stop if input `data` has no columns
80+
if (ncol(data) == 0) {
81+
stop("The input `data` table must have at least one column.",
82+
call. = FALSE)
83+
}
84+
7985
opts_df <- gt_options_default()
8086

8187
# Add the table ID to the `id` parameter
8288
if (!is.null(id)) {
83-
84-
opts_df <- opts_df_set(
85-
opts_df, "table_id", id)
89+
opts_df <- opts_df_set(opts_df, "table_id", id)
8690
}
8791

8892
# If the option to place rownames in the stub
@@ -150,6 +154,13 @@ gt <- function(data,
150154
data[[groupname_col]] <- NULL
151155
}
152156

157+
# Stop if input `data` has no columns (after modifying
158+
# `data` for groups)
159+
if (ncol(data) == 0) {
160+
stop("The `data` must have at least one column that isn't a 'group' column.",
161+
call. = FALSE)
162+
}
163+
153164
# Take the input data and convert to a
154165
# data frame
155166
data_tbl <-
@@ -182,7 +193,11 @@ gt <- function(data,
182193
)[-1, ]
183194

184195
# Create a prepopulated `rows_df` data frame
185-
rows_df <- dplyr::tibble(rownums_start = seq(nrow(data_tbl)))
196+
if (nrow(data_tbl) > 0) {
197+
rows_df <- dplyr::tibble(rownums_start = seq(nrow(data_tbl)))
198+
} else {
199+
rows_df <- dplyr::tibble(rownums_start = NA_integer_)[-1, ]
200+
}
186201

187202
# Create a prepopulated `cols_df` data frame
188203
cols_df <- dplyr::tibble(colnames_start = colnames(data_tbl))
@@ -192,17 +207,19 @@ gt <- function(data,
192207
# data frames that contain specialized formatting
193208
# directives that will be used during render time
194209
empty_df <- data_tbl
195-
empty_df[] <- NA_character_
210+
if (nrow(data_tbl) > 0) {
211+
empty_df[] <- NA_character_
212+
}
196213

197214
# Create a data frame that represents the table's
198215
# columns (`boxh_df`); each row has a special
199216
# meaning and this will be used during render time
200-
boxh_df <- empty_df[c(), , drop = FALSE]
201-
boxh_df[1:3, ] = list(NA_character_)
202-
203-
# Assign rownames to the `boxh_df` for easier
204-
# manipulation of rows
205-
rownames(boxh_df) <- c("group_label", "column_label", "column_align")
217+
boxh_df <-
218+
matrix(data = NA_character_, nrow = 3, ncol = ncol(data_tbl)) %>%
219+
as.data.frame() %>%
220+
dplyr::mutate_all(as.character) %>%
221+
magrittr::set_names(names(data_tbl)) %>%
222+
magrittr::set_rownames(c("group_label", "column_label", "column_align"))
206223

207224
# Apply initialized data frames as attributes
208225
# within the object

R/utils_render_common.R

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ rownum_translation <- function(output_df,
5353
initialize_output_df <- function(data_df) {
5454

5555
output_df <- data_df
56+
57+
if (nrow(output_df) > 0) {
5658
output_df[] <- NA_character_
59+
}
60+
5761
output_df
5862
}
5963

@@ -832,3 +836,35 @@ create_summary_rows <- function(n_rows,
832836
unlist() %>%
833837
unname()
834838
}
839+
840+
#' Suitably replace `NA` values in the `groups_df` data frame
841+
#'
842+
#' @param groups_df The `groups_df` data frame.
843+
#' @param others_group The `others_group` vector.
844+
#' @noRd
845+
replace_na_groups_df <- function(groups_df,
846+
others_group) {
847+
848+
if (nrow(groups_df) > 0) {
849+
groups_df[is.na(groups_df[, "groupname"]), "groupname"] <- others_group
850+
}
851+
852+
groups_df
853+
}
854+
855+
#' Suitably replace `NA` values in the `groups_rows_df` data frame
856+
#'
857+
#' @param groups_rows_df The `groups_rows_df` data frame.
858+
#' @param others_group The `others_group` vector.
859+
#' @noRd
860+
replace_na_groups_rows_df <- function(groups_rows_df,
861+
others_group) {
862+
863+
if (nrow(groups_rows_df) > 0) {
864+
groups_rows_df[
865+
is.na(groups_rows_df[, "group"]),
866+
c("group", "group_label")] <- others_group
867+
}
868+
869+
groups_rows_df
870+
}

R/utils_render_html.R

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,28 +12,6 @@ footnote_glyph_to_html <- function(footnote_glyph) {
1212
as.character()
1313
}
1414

15-
#' Split the body content vector into a list structure
16-
#'
17-
#' Taking the `body_content` vector, split into list components with one item
18-
#' per row in the output table
19-
#' @noRd
20-
split_body_content <- function(body_content,
21-
n_cols) {
22-
23-
split(body_content, ceiling(seq_along(body_content) / n_cols))
24-
}
25-
26-
#' Split the body content vector into a list structure
27-
#'
28-
#' Taking the `body_content` vector, split into list components with one item
29-
#' per row in the output table
30-
#' @noRd
31-
split_body_content <- function(body_content,
32-
n_cols) {
33-
34-
split(body_content, ceiling(seq_along(body_content) / n_cols))
35-
}
36-
3715
styles_to_html <- function(styles) {
3816

3917
style_list <-

R/utils_render_rtf.R

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,3 +350,18 @@ rtf_last_body_row <- function(content) {
350350

351351
output
352352
}
353+
354+
#' Split the body content vector into a list structure
355+
#'
356+
#' Taking the `body_content` vector, split into list components with one item
357+
#' per row in the output table
358+
#' @noRd
359+
split_body_content <- function(body_content,
360+
n_cols) {
361+
362+
if (length(body_content) == 0) {
363+
return(list(rep("", n_cols)))
364+
}
365+
366+
split(body_content, ceiling(seq_along(body_content) / n_cols))
367+
}

tests/testthat/helper-gt_attr_expectations.R

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,18 @@ expect_tab <- function(tab,
7070

7171
# Expect that the attribute objects are of the
7272
# correct dimensions
73+
if (dplyr::is_grouped_df(df)) {
7374

74-
final_df <- df
75+
non_group_cols <- base::setdiff(colnames(df), dplyr::group_vars(df))
76+
77+
final_df <-
78+
df %>%
79+
dplyr::ungroup() %>%
80+
dplyr::select(non_group_cols)
81+
82+
} else {
83+
final_df <- df
84+
}
7585

7686
if (has_rownames) {
7787
final_df$rowname <- NULL

tests/testthat/test-gt_object.R

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,26 @@ test_that("a gt table object contains the correct components", {
55
# Create a `gt_tbl` object with `gt()`
66
tab <- gt(data = iris)
77

8+
# Expect that the `gt_tbl` object has all of the
9+
# usual components and that they have all of the
10+
# expected dimensions and features
811
expect_tab(tab, iris)
912

13+
# Expect that the `stub_df` data frame is correctly
14+
# formed given the input rownames and groupnames
1015
expect_tab_colnames(
1116
tab, df = iris,
1217
rowname = "NA",
1318
groupname_is_na = TRUE)
1419

20+
# Create a `gt_tbl` object with `gt()` and a
21+
# grouped version of the `iris` dataset
22+
tab <- gt(data = iris %>% dplyr::group_by(Species))
23+
24+
# Expect that the `gt_tbl` object has all of the
25+
# usual components and that they have all of the
26+
# expected dimensions and features
27+
expect_tab(tab, df = iris %>% dplyr::group_by(Species))
1528
})
1629

1730
test_that("a gt table can be made to use the rownames of a data frame", {
@@ -20,8 +33,13 @@ test_that("a gt table can be made to use the rownames of a data frame", {
2033
# data frame's row names as row names in the stub
2134
tab <- gt(data = mtcars, rownames_to_stub = TRUE)
2235

36+
# Expect that the `gt_tbl` object has all of the
37+
# usual components and that they have all of the
38+
# expected dimensions and features
2339
expect_tab(tab, mtcars)
2440

41+
# Expect that the `stub_df` data frame is correctly
42+
# formed given the input rownames and groupnames
2543
expect_tab_colnames(
2644
tab, df = mtcars,
2745
rowname = "tibble",
@@ -42,8 +60,13 @@ test_that("a gt table can be made with the stub partially or fully populated", {
4260
# `data_r` dataset
4361
tab <- gt(data = data_r)
4462

63+
# Expect that the `gt_tbl` object has all of the
64+
# usual components and that they have all of the
65+
# expected dimensions and features
4566
expect_tab(tab, data_r, has_rownames = TRUE)
4667

68+
# Expect that the `stub_df` data frame is correctly
69+
# formed given the input rownames and groupnames
4770
expect_tab_colnames(
4871
tab, df = data_r,
4972
rowname = "col",
@@ -62,11 +85,46 @@ test_that("a gt table can be made with the stub partially or fully populated", {
6285
# `data_rg` dataset
6386
tab <- gt(data = data_rg)
6487

88+
# Expect that the `gt_tbl` object has all of the
89+
# usual components and that they have all of the
90+
# expected dimensions and features
6591
expect_tab(
6692
tab, data_rg, has_groupnames = TRUE, has_rownames = TRUE)
6793

94+
# Expect that the `stub_df` data frame is correctly
95+
# formed given the input rownames and groupnames
6896
expect_tab_colnames(
6997
tab, df = data_rg,
7098
rowname = "col",
7199
groupname_is_na = FALSE)
72100
})
101+
102+
test_that("a gt table can be made from a table with no rows", {
103+
104+
# Create an input data frame based on the exibble
105+
# dataset, except with no rows
106+
data_e <- exibble %>% head(0)
107+
108+
# Create a `gt_tbl` object with `gt()` and the
109+
# `data_e` dataset
110+
tab <- gt(data = data_e)
111+
112+
# Expect that the `gt_tbl` object has all of the
113+
# usual components and that they have all of the
114+
# expected dimensions and features
115+
expect_tab(tab, data_e)
116+
117+
# Expect that the `stub_df` data frame is empty
118+
attr(tab, "stub_df") %>%
119+
nrow() %>%
120+
expect_equal(0)
121+
122+
# Create a `gt_tbl` object with `gt()` and a
123+
# grouped version of the `data_e` dataset
124+
tab <- gt(data = data_e %>% dplyr::group_by(group))
125+
126+
# Expect that the `gt_tbl` object has all of the
127+
# usual components and that they have all of the
128+
# expected dimensions and features
129+
expect_tab(tab, df = data_e %>% dplyr::group_by(group))
130+
})

0 commit comments

Comments
 (0)