Skip to content

Commit a956317

Browse files
committed
progress
1 parent 4fe2305 commit a956317

10 files changed

+688
-44
lines changed

DESCRIPTION

+4-3
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ BugReports: https://github.com/ddsjoberg/gtsummary/issues
4545
Depends:
4646
R (>= 4.1)
4747
Imports:
48-
cards (>= 0.1.0.9016),
48+
cards (>= 0.1.0.9020),
4949
cli (>= 3.6.1),
5050
dplyr (>= 1.1.3),
5151
glue (>= 1.6.2),
@@ -55,9 +55,10 @@ Imports:
5555
tibble (>= 3.2.1),
5656
tidyr (>= 1.3.0)
5757
Suggests:
58-
cardx (>= 0.1.0.9031),
58+
cardx (>= 0.1.0.9033),
5959
knitr,
60-
testthat (>= 3.2.0)
60+
testthat (>= 3.2.0),
61+
withr
6162
VignetteBuilder:
6263
knitr
6364
RdMacros:

NAMESPACE

+1
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,4 @@ importFrom(dplyr,select)
7474
importFrom(dplyr,starts_with)
7575
importFrom(dplyr,vars)
7676
importFrom(dplyr,where)
77+
importFrom(glue,glue)

NEWS.md

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
* Added a family of function `styfn_*()` that are similar to the `style_*()` except they return a styling _function_, rather than a styled value.
1212

13-
* Previously, in a `tbl_summary()` variables that were `c(0, 1)`, `c("no", "yes")`, `c("No", "Yes")`, and `c("NO", "YES")` would default to a dichotomous summary with the `1` and `yes` level being shown in the table. This would occur even in the case when, for example, only `0` was observed. In this release, the line shown for dichotomous variables must be observed OR the unobserved level must be either explicitly defined in a factor or be a logical vector. This means that a vector of all `"yes"` values will default to a categorical summary.
13+
* Previously, in a `tbl_summary()` variables that were `c(0, 1)`, `c("no", "yes")`, `c("No", "Yes")`, and `c("NO", "YES")` would default to a dichotomous summary with the `1` and `yes` level being shown in the table. This would occur even in the case when, for example, only `0` was observed. In this release, the line shown for dichotomous variables must be observed OR the unobserved level must be either explicitly defined in a factor or be a logical vector. This means that a character vector of all `"yes"` or all `"no"` values will default to a categorical summary instead of dichotomous.
1414

1515
* Previously, indentation was handled with `modify_table_styling(text_format = c("indent", "indent2"))`, which would indent a cell 4 and 8 spaces, respectively. Handling of indentation has been migrated to `modify_table_styling(indentation = integer())`, and by default, the label column is indented to zero spaces. This makes it easier to indent a group of rows.
1616

@@ -22,7 +22,7 @@
2222

2323
* The values passed in `tbl_summary(value)` are now only checked for columns that are summary type `"dichotomous"`.
2424

25-
* Previously, the gtsummary selecting functions, e.g. `all_categorical()`, `all_continuous()`, etc., would error if used out of context. Now they won't select the columns silently.
25+
* Previously, the gtsummary selecting functions, e.g. `all_categorical()`, `all_continuous()`, etc., would error if used out of context. They will now select no columns when used out-of-context.
2626

2727
#### Internal Updates
2828

@@ -38,6 +38,12 @@
3838

3939
* The `modify_header(stat_by)` argument was deprecated in v1.3.6 (2021-01-08), and has now been fully removed from the package.
4040

41+
* Use of the `vars()` selector was first removed in v1.2.5 (2020-02-11), and the messaging about the deprecation was kicked up in June 2022. This use is now defunct and the function will soon no longer be exported.
42+
43+
* The `add_p(test = ~'aov')` test is now deprecated as identical results can be obtained with `add_p(test = ~'oneway.test', test.args = ~list(var.equal = TRUE))`.
44+
45+
* Previously, `add_p.tbl_summary()` would coerce various data types to classes compatible with some base R tests. One example, is that we would convert `difftime` classes to general numeric before passing to `wilcox.test()`. We have eliminated type- and class-specific handling in these functions and it is now left to the the user pass data compatible with the functions that calculate the p-values or to create a custom test that wraps `wilcox.test()` and performs the conversion. This change is effective immediately.
46+
4147
# gtsummary 1.7.2
4248

4349
* Removed messaging about the former auto-removal of the `tbl_summary(group)` variable from the table: a change that occurred 3+ years ago in gtsummary v1.3.1

R/add_p.R

+67-19
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ add_p.tbl_summary <- function(x,
8787
# checking that input x has a by var
8888
if (is_empty(x$inputs$by)) {
8989
"Cannot run {.fun add_p} when {.code tbl_summary(by)} argument not included." |>
90-
cli::cli_abort()
90+
cli::cli_abort(call = get_cli_abort_call())
9191
}
9292

9393
cards::process_selectors(
@@ -133,16 +133,34 @@ add_p.tbl_summary <- function(x,
133133
)
134134

135135
# add all available test meta data to a data frame ---------------------------
136-
df_test_meta_data <- .test_meta_data(test)
136+
df_test_meta_data <-
137+
imap(
138+
test,
139+
~dplyr::tibble(variable = .y, fun_to_run = list(.x), test_name = attr(.x, "test_name") %||% NA_character_)
140+
) |>
141+
dplyr::bind_rows()
137142

138143
# add test names to `.$table_body` so it can be used in selectors ------------
139-
x$table_body <-
140-
dplyr::left_join(
141-
x$table_body,
142-
df_test_meta_data[c("variable", "test_name")],
143-
by = "variable"
144-
) |>
145-
dplyr::relocate("test_name", .after = "variable")
144+
if (!"test_name" %in% names(x$table_body)) {
145+
x$table_body <-
146+
dplyr::left_join(
147+
x$table_body,
148+
df_test_meta_data[c("variable", "test_name")],
149+
by = "variable"
150+
) |>
151+
dplyr::relocate("test_name", .after = "variable")
152+
}
153+
else {
154+
x$table_body <-
155+
dplyr::rows_update(
156+
x$table_body,
157+
df_test_meta_data[c("variable", "test_name")],
158+
by = "variable",
159+
unmatched = "ignore"
160+
) |>
161+
dplyr::relocate("test_name", .after = "variable")
162+
}
163+
146164

147165
# now process the `test.args` argument ---------------------------------------
148166
cards::process_formula_selectors(
@@ -185,8 +203,7 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
185203
df_test_meta_data |>
186204
dplyr::filter(.data$variable %in% .env$variable) |>
187205
dplyr::pull("fun_to_run") %>%
188-
getElement(1) |>
189-
eval(),
206+
getElement(1),
190207
args = list(
191208
data = x$inputs$data,
192209
variable = variable,
@@ -198,6 +215,14 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
198215
)
199216
)
200217

218+
# if there was a warning captured, print it now
219+
if (!is.null(lst_captured_results[["warning"]])) {
220+
cli::cli_inform(c(
221+
"The following warning was returned in {.fun {calling_fun}} for variable {.val {variable}}",
222+
"!" = lst_captured_results[["warning"]]
223+
))
224+
}
225+
201226
# if test evaluated without error, return the result
202227
if (!is.null(lst_captured_results[["result"]])) return(lst_captured_results[["result"]]) # styler: off
203228
# otherwise, construct a {cards}-like object with error
@@ -206,7 +231,7 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
206231
variable = variable,
207232
stat_name = switch(calling_fun, "add_p" = "p.value", "add_difference" = "estimate"),
208233
stat = list(NULL),
209-
warning = lst_captured_results["result"],
234+
warning = lst_captured_results["warning"],
210235
error = lst_captured_results["error"]
211236
) %>%
212237
structure(., class = c("card", class(.)))
@@ -217,10 +242,14 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
217242
# print any errors or warnings
218243
lst_results |>
219244
map(\(x) if (inherits(x, "card")) x else NULL) |>
220-
dplyr::bind_rows() |>
221-
dplyr::filter(.data$stat_name %in% c("estimate", "std.error", "parameter", "statistic",
222-
"conf.low", "conf.high", "p.value")) |>
223-
cards::print_ard_conditions()
245+
dplyr::bind_rows() %>%
246+
{switch(
247+
!is_empty(.),
248+
dplyr::filter(., .data$stat_name %in% c("estimate", "std.error", "parameter", "statistic",
249+
"conf.low", "conf.high", "p.value")) |>
250+
cards::print_ard_conditions()
251+
)}
252+
224253

225254
# combine results into a single data frame
226255
df_results <-
@@ -251,6 +280,16 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
251280
) |>
252281
dplyr::bind_rows()
253282

283+
# remove new columns that already exist in gtsummary table
284+
new_columns <- names(df_results) |> setdiff(names(x$table_body))
285+
if (is_empty(new_columns)) {
286+
cli::cli_abort(
287+
c("Columns {.val {names(df_results) |> setdiff('variable')}} are already present in table (although, some may be hidden), and no new columns were added.",
288+
i = "Use {.code tbl |> modify_table_body(\\(x) dplyr::select(x, -p.value))} to remove columns and they will be replaced by the new columns from the current call."),
289+
call = get_cli_abort_call()
290+
)
291+
}
292+
254293
# create default footnote text
255294
footnote <- map(
256295
lst_results,
@@ -270,6 +309,7 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
270309
unlist() |>
271310
unique() |>
272311
paste(collapse = "; ")
312+
if (footnote == "" || is_empty(footnote)) footnote <- NULL
273313

274314
# add results to `.$table_body` ----------------------------------------------
275315
x <- x |>
@@ -284,11 +324,21 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
284324
x <-
285325
modify_table_styling(
286326
x,
287-
columns = any_of("p.value"),
327+
columns = intersect("p.value", new_columns),
288328
label = "**p-value**",
289329
hide = FALSE,
290330
fmt_fun = styfn_pvalue(),
291331
footnote = footnote
332+
) |>
333+
modify_table_styling(
334+
columns =
335+
intersect(
336+
c("estimate", "std.error", "parameter", "statistic", "conf.low", "conf.high"),
337+
new_columns
338+
),
339+
hide = TRUE,
340+
fmt_fun = styfn_sigfig(),
341+
footnote = footnote
292342
)
293343

294344
# adding labels for hidden columns
@@ -298,8 +348,6 @@ calculate_and_add_test_results <- function(x, include, group, test.args, adj.var
298348
) |>
299349
tidyr::fill("modify_stat_N", .direction = "downup") # fill missing N for new cols
300350

301-
302-
303351
# add raw results to `.$card`
304352
x$cards[[calling_fun]] <- lst_results
305353

R/assign_tests.R

+60-11
Original file line numberDiff line numberDiff line change
@@ -52,30 +52,77 @@ assign_tests.tbl_summary <- function(x, test = NULL, group = NULL, include,
5252
lapply(
5353
include,
5454
function(variable) {
55-
# if there is a user-supplied test, use that one
56-
if (!is.null(test[[variable]])) return(test[[variable]]) # styler: off
57-
58-
if (calling_fun %in% "add_p") {
59-
default_test <-
60-
add_p_tbl_summary_default_test(data, variable = variable,
61-
by = by, group = group,
62-
summary_type = summary_type[[variable]])
55+
if (is.null(test[[variable]]) && calling_fun %in% "add_p") {
56+
test[[variable]] <-
57+
.add_p_tbl_summary_default_test(data, variable = variable,
58+
by = by, group = group,
59+
summary_type = summary_type[[variable]])
6360
}
6461

65-
if (is.null(default_test)) {
62+
if (is.null(test[[variable]])) {
6663
cli::cli_abort(c(
6764
"There is no default test set for column {.val {variable}}.",
6865
i = "Set a value in the {.arg test} argument for column {.val {variable}} or exclude with {.code include = -{variable}}."),
6966
call = get_cli_abort_call()
7067
)
7168
}
72-
default_test
69+
70+
test[[variable]] <-
71+
.process_test_argument_value(
72+
test = test[[variable]],
73+
class = "tbl_summary",
74+
calling_fun = calling_fun
75+
)
7376
}
7477
) |>
7578
stats::setNames(include)
7679
}
7780

78-
add_p_tbl_summary_default_test <- function(data, variable, by, group, summary_type) {
81+
82+
.process_test_argument_value <- function(test, class, calling_fun) {
83+
# subset the data frame
84+
df_tests <-
85+
df_add_p_tests |>
86+
dplyr::filter(.data$class %in% .env$class, .data[[calling_fun]])
87+
88+
# if the test is character and it's an internal test
89+
if (is.character(test) && test %in% df_tests$test_name) {
90+
test_to_return <- df_tests$fun_to_run[df_tests$test_name %in% test][[1]] |> eval()
91+
attr(test_to_return, "test_name") <- df_tests$test_name[df_tests$test_name %in% test]
92+
return(test_to_return)
93+
}
94+
95+
# if the test is character and it's NOT an internal test
96+
if (is.character(test)) {
97+
return(eval(parse_expr(test), envir = attr(test, ".Environment")))
98+
}
99+
100+
# if passed test is a function and it's an internal test
101+
internal_test_index <- df_tests$test_fun |>
102+
map_lgl(~identical_no_attr(eval(.x), test)) |>
103+
which()
104+
if (is.function(test) && !is_empty(internal_test_index)) {
105+
test_to_return <- df_add_p_tests$fun_to_run[[internal_test_index]] |> eval()
106+
attr(test_to_return, "test_name") <- df_add_p_tests$test_name[internal_test_index]
107+
return(test_to_return)
108+
}
109+
110+
# otherwise, if it's a function, return it
111+
return(eval(test, envir = attr(test, ".Environment")))
112+
113+
}
114+
115+
# compare after removing attributes
116+
identical_no_attr <- function(x, y) {
117+
tryCatch({
118+
attributes(x) <- NULL
119+
attributes(y) <- NULL
120+
identical(x, y)},
121+
error = \(x) FALSE
122+
)
123+
}
124+
125+
.add_p_tbl_summary_default_test <- function(data, variable, by, group, summary_type) {
79126
# for continuous data, default to non-parametric tests
80127
if (is_empty(group) && summary_type %in% c("continuous", "continuous2") && length(unique(data[[by]])) == 2) {
81128
test_func <-
@@ -131,4 +178,6 @@ add_p_tbl_summary_default_test <- function(data, variable, by, group, summary_ty
131178
return(test_func)
132179
}
133180
}
181+
182+
return(NULL)
134183
}

R/gtsummary-package.R

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#' @keywords internal
22
#' @import rlang
33
#' @importFrom dplyr across
4+
#' @importFrom glue glue
45
"_PACKAGE"
56

67
## usethis namespace: start

R/reexport.R

+1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ dplyr::where
5959
#' @export
6060
dplyr::one_of
6161

62+
# Remove after Jan 1, 2025
6263
#' @importFrom dplyr vars
6364
#' @export
6465
dplyr::vars

0 commit comments

Comments
 (0)