Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor as_rtf() #450

Merged
merged 44 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
d01df19
tweak error message
yihui Aug 12, 2024
5485aec
remove the redundant paste0() call: paste(collapse = ' | ') will jus…
yihui Aug 12, 2024
1a0da68
introduce the %||% operator
yihui Aug 12, 2024
fc0d3e1
merge the two assignments
yihui Aug 12, 2024
52a5165
"weld" the few short pipes into one expression
yihui Aug 12, 2024
cb14c1b
factor out the code to generate the default title and footnote to be …
yihui Aug 12, 2024
4c92673
an alternative way to find the method
yihui Aug 12, 2024
5e493b5
perhaps we should use `colname_spannersub[2]` as in `as_gt()`, instea…
yihui Aug 12, 2024
6dd6164
copy from as_gt()
yihui Aug 12, 2024
becd0e0
copying from as_gt(): the argument `display_inf_bound` was not actual…
yihui Aug 12, 2024
642e498
rename design_method() to fixed_design_method()
yihui Aug 12, 2024
e271130
factor out gs_design_method()
yihui Aug 12, 2024
c2c640e
copy from as_gt() (will see how to refactor the code later)
yihui Aug 12, 2024
26c3d2b
copy from as_rtf.fixed_design()
yihui Aug 12, 2024
6b34090
use %||%
yihui Aug 12, 2024
98c0bca
2:length is equivalent to -1 (well, assuming length >= 2)
yihui Aug 12, 2024
680fc01
simplify these border variables a little bit
yihui Aug 12, 2024
ee5387a
intToUtf8() sounds like an overkill; we can simply index the `letters…
yihui Aug 12, 2024
709bc05
factor out the row/column indices to make [] thinner
yihui Aug 12, 2024
a16e25c
I don't think `footnotes` can be NULL
yihui Aug 12, 2024
f705696
this element should be hard-coded as `"Null hypothesis"` (otherwise i…
yihui Aug 13, 2024
bb3397a
factor out more strings into footnote_content()
yihui Aug 13, 2024
b0fb333
reuse footnote_content() and eliminate line breaks (which don't matte…
yihui Aug 13, 2024
73370df
amend c2c640e5306ed5e9c4002f14ac6c68ef2ac91d11: factor out the defaul…
yihui Aug 13, 2024
9c7308a
it may be more readable without using a closure from local()
yihui Aug 13, 2024
ebfb904
rename `method_title` to `fixed_method_title`, and add `gs_method_tit…
yihui Aug 13, 2024
7899504
factor out the code to get/transform `display_columns`
yihui Aug 13, 2024
0da4be1
shorten function names
yihui Aug 13, 2024
b5ba260
make this function a one-liner based on the fact that an assignment w…
yihui Aug 13, 2024
584f485
cosmetic changes
yihui Aug 13, 2024
4e321d2
factor out the code to add footnotes and write RTF
yihui Aug 13, 2024
998ef29
delete an extra space (nobody has discovered this before? unbelievable)
yihui Aug 13, 2024
bd00877
factor out the code to check the `col_rel_width`
yihui Aug 13, 2024
247b00e
factor out more common code from as_gt.gs_design() and as_rtf.gs_desi…
yihui Aug 13, 2024
476a635
collect footnotes using c() and concatenate all with paste(..., colla…
yihui Aug 13, 2024
ac4a83d
move the logic `inherits('non_binding') & x_alpha < full_alpha` into …
yihui Aug 13, 2024
49b23a9
factor out `substr(Analysis, 1, 11) == 'Analysis: N'`, which is poten…
yihui Aug 13, 2024
9522d70
the functions `gs_[sub]title` are simple enough and not used elsewher…
yihui Aug 13, 2024
a598ec6
use the processed `x` from `info` so that the length of `footnote_row…
yihui Aug 13, 2024
3654e0f
bump version
yihui Aug 13, 2024
5397198
more comments
yihui Aug 14, 2024
954a226
misplaced ) [ci skip]
yihui Aug 14, 2024
d353d81
Merge branch 'main' into as_rtf_tweaks
yihui Aug 16, 2024
12cec74
renaming using the prefixes `fd_` and `gsd_`
yihui Aug 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: gsDesign2
Title: Group Sequential Design with Non-Constant Effect
Version: 1.1.2.20
Version: 1.1.2.21
Authors@R: c(
person("Keaven", "Anderson", email = "[email protected]", role = c("aut")),
person("Yilong", "Zhang", email = "[email protected]", role = c("aut")),
Expand Down
257 changes: 137 additions & 120 deletions R/as_gt.R
Original file line number Diff line number Diff line change
Expand Up @@ -79,23 +79,37 @@ as_gt <- function(x, ...) {
#' summary() %>%
#' as_gt()
as_gt.fixed_design <- function(x, title = NULL, footnote = NULL, ...) {
# get the design method
design_mtd <- intersect(
c("ahr", "fh", "mb", "lf", "rd", "maxcombo", "milestone", "rmst"), class(x)
)[1]
method <- fd_method(x)
ans <- gt::gt(x) %>%
gt::tab_header(title = title %||% fd_title(method)) %>%
gt::tab_footnote(
footnote = footnote %||% fd_footnote(x, method),
locations = gt::cells_title(group = "title")
)
return(ans)
}

# get the fixed design method
fd_method <- function(x) {
methods <- c("ahr", "fh", "mb", "lf", "rd", "maxcombo", "milestone", "rmst")
intersect(methods, class(x))[1]
}

# set the default title
if (is.null(title)) title <- sprintf("Fixed Design %s Method", switch(
design_mtd,
# get the default title
fd_title <- function(method) {
sprintf("Fixed Design %s Method", switch(
method,
ahr = "under AHR", fh = "under Fleming-Harrington", mb = "under Magirr-Burman",
lf = "under Lachin and Foulkes", maxcombo = "under MaxCombo",
milestone = "under Milestone", rmst = "under Restricted Mean Survival Time",
rd = "of Risk Difference under Farrington-Manning"
))
}

# set the default footnote
if (is.null(footnote)) footnote <- switch(
design_mtd,
# get the default footnote
fd_footnote <- function(x, method) {
switch(
method,
ahr = "Power computed with average hazard ratio method.",
fh = paste(
"Power for Fleming-Harrington test", substring(x$Design, 19),
Expand All @@ -116,12 +130,6 @@ as_gt.fixed_design <- function(x, title = NULL, footnote = NULL, ...) {
# for mb, milestone, and rmst
paste("Power for", x$Design, "computed with method of Yung and Liu.")
)

ans <- gt::gt(x) %>%
gt::tab_header(title = title) %>%
gt::tab_footnote(footnote = footnote, locations = gt::cells_title(group = "title"))

return(ans)
}

#' @rdname as_gt
Expand Down Expand Up @@ -242,115 +250,24 @@ as_gt.gs_design <- function(
display_inf_bound = FALSE,
...) {

method <- intersect(class(x), c("ahr", "wlr", "combo", "rd"))[1]
full_alpha <- attr(x, "full_alpha")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LittleBeannie I have a question regarding a difference between as_gt() and as_rtf() that I noticed while comparing the two: for as_gt(), we obtain full_alpha from the attribute of x, whereas for as_rtf(), full_alpha is its argument and not retrieved from x's attributes. Is this expected? I'm not sure if you or @elong0527 is the right person to ask.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @yihui , yes, I can answer the question. The full_alpha should be retrieved from x's attributes. I will prepare a PR to get it there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to do it, please wait until this PR is merged, or I can just do it in this PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the confirmation. I know there are many changes occurring in this pull request, so let's wait until this PR is merged.


x_alpha <- max(filter(x, Bound == display_bound[1])[[colname_spannersub[2]]])

x_non_binding <- inherits(x, "non_binding")

x_k <- as.numeric(substr(x$Analysis, 11, 11))

if (!display_inf_bound) x <- filter(x, !is.infinite(Z))

x_old <- x

# Set defaults ----
# set different default title to different methods
if (is.null(title)) title <- paste("Bound summary", switch(
method,
ahr = "for AHR design", wlr = "for WLR design",
combo = "for MaxCombo design", rd = "of Binary Endpoint"
))

# set different default subtitle to different methods
if (is.null(subtitle)) subtitle <- switch(
method,
ahr = "AHR approximations of ~HR at bound",
wlr = "WLR approximation of ~wHR at bound",
combo = "MaxCombo approximation",
rd = "measured by risk difference"
)

# set different default columns to display
if (is.null(display_columns)) display_columns <- c(
"Analysis", "Bound", "Z", "Nominal p",
sprintf("%s at bound", switch(method, ahr = "~HR", wlr = "~wHR", rd = "~Risk difference")),
"Alternate hypothesis", "Null hypothesis"
)
# filter the columns to display as the output
## if `Probability` is selected to output, then transform it to `c("Alternate hypothesis", "Null hypothesis")`
if (any(i <- display_columns == "Probability"))
display_columns <- c(display_columns[!i], "Alternate hypothesis", "Null hypothesis")
## check if the `display_columns` are included in `x` output
if (!all(display_columns %in% names(x))) stop(
"not all variable names in 'display_columns' are in the summary_bound object!"
)
x <- x[, display_columns]

# set different default footnotes to different methods
if (is.null(footnote)) footnote <- switch(
method,
ahr = list(
content = c(
if (i1 <- "~HR at bound" %in% display_columns)
"Approximate hazard ratio to cross bound.",
if (i2 <- "Nominal p" %in% display_columns)
"One-sided p-value for experimental vs control treatment.
Value < 0.5 favors experimental, > 0.5 favors control."
),
location = c(if (i1) "~HR at bound", if (i2) "Nominal p"),
attr = c(if (i1) "colname", if (i2) "colname")
),
wlr = list(
content = c(
if (i1 <- "~wHR at bound" %in% display_columns)
"Approximate hazard ratio to cross bound.",
if (i2 <- "Nominal p" %in% display_columns)
"One-sided p-value for experimental vs control treatment.
Value < 0.5 favors experimental, > 0.5 favors control.",
"wAHR is the weighted AHR."
),
location = c(if (i1) "~wHR at bound", if (i2) "Nominal p"),
attr = c(if (i1) "colname", if (i2) "colname", "analysis")
),
combo = list(
content = c(
if (i2 <- "Nominal p" %in% display_columns)
"One-sided p-value for experimental vs control treatment.
Value < 0.5 favors experimental, > 0.5 favors control.",
"EF is event fraction. AHR is under regular weighted log rank test."),
location = if (i2) "Nominal p",
attr = c(if (i2) "colname", "analysis")
),
rd = list(
content = if (i2 <- "Nominal p" %in% display_columns)
"One-sided p-value for experimental vs control treatment.
Value < 0.5 favors experimental, > 0.5 favors control.",
location = if (i2) "Nominal p",
attr = if (i2) "colname"
)
full_alpha <- attr(x, "full_alpha")
parts <- gsd_parts(
x, title, subtitle, colname_spannersub, footnote,
display_bound, display_columns, display_inf_bound
)

# Filter out inf bound ----
x <- subset(x, !is.na(`Alternate hypothesis`) & !is.na(`Null hypothesis`))

# Add spanner ----
i <- match(c("Alternate hypothesis", "Null hypothesis"), names(x))
names(x)[i] <- colname_spannersub

x <- x %>%
subset(Bound %in% display_bound) %>%
dplyr::arrange(Analysis) %>%
x <- parts$x %>%
dplyr::group_by(Analysis) %>%
gt::gt() %>%
gt::tab_spanner(
columns = dplyr::all_of(colname_spannersub),
label = colname_spanner
) %>%
gt::tab_header(title = title, subtitle = subtitle)
gt::tab_header(title = parts$title, subtitle = parts$subtitle)

# Add footnotes ----
footnote <- parts$footnote
for (i in seq_along(footnote$content)) {
att <- footnote$attr[i]
loc <- if (att == "colname") {
Expand All @@ -370,21 +287,69 @@ as_gt.gs_design <- function(
x <- gt::tab_footnote(x, footnote = footnote$content[i], locations = loc)
}

## if it is non-binding design
if (x_non_binding && x_alpha < full_alpha) x <- gt::tab_footnote(
# add footnote for non-binding design
footnote_nb <- gsd_footnote_nb(x_old, parts$alpha, full_alpha)
if (!is.null(footnote_nb)) x <- gt::tab_footnote(
x,
footnote = footnote_non_binding(x_alpha, full_alpha),
footnote = footnote_nb,
locations = gt::cells_body(
columns = colname_spannersub[2],
rows = substr(x_old$Analysis, 1, 11) == paste0("Analysis: ", max(x_k)) &
x_old$Bound == display_bound[1]
rows = gsd_footnote_row(parts$x, display_bound[1])
)
)

return(x)
}

footnote_non_binding <- function(x_alpha, full_alpha) {
# get different default columns to display
gsd_columns <- function(columns, method, x) {
# set different default columns to display
if (is.null(columns)) columns <- c(
"Analysis", "Bound", "Z", "Nominal p",
sprintf("%s at bound", switch(method, ahr = "~HR", wlr = "~wHR", rd = "~Risk difference")),
"Alternate hypothesis", "Null hypothesis"
)
# filter the columns to display as the output: if `Probability` is selected to
# output, transform it to `c("Alternate hypothesis", "Null hypothesis")`
if (any(i <- columns == "Probability"))
columns <- c(columns[!i], "Alternate hypothesis", "Null hypothesis")
## check if the `display_columns` are included in `x` output
if (!all(columns %in% names(x))) stop(
"not all variable names in 'display_columns' are in the summary_bound object!"
)
columns
}

# default footnotes for 'gs_design' tables
gsd_footnote <- function(method, columns) {
n <- c("Nominal p", "~HR at bound", "~wHR at bound")
i <- n %in% columns
res <- if (i[1]) list(
content = paste(
"One-sided p-value for experimental vs control treatment.",
"Value < 0.5 favors experimental, > 0.5 favors control."
),
location = n[1], attr = "colname"
) else {
list(content = NULL, location = NULL, attr = NULL)
}
x <- "Approximate hazard ratio to cross bound."
switch(
method,
ahr = res %+% if (i[2]) list(x, n[2], "colname"),
wlr = res %+% (if (i[3]) list(x, n[3], "colname")) %+%
list("wAHR is the weighted AHR.", NULL, "analysis"),
combo = res %+% list(
"EF is event fraction. AHR is under regular weighted log rank test.",
NULL, "analysis"
),
rd = res
)
}

# footnote for non-binding designs
gsd_footnote_nb <- function(x, x_alpha, full_alpha) {
if (!inherits(x, "non_binding") || x_alpha >= full_alpha) return()
a1 <- format(x_alpha, scientific = FALSE)
a2 <- format(full_alpha, scientific = FALSE)
a3 <- format(full_alpha - x_alpha, scientific = FALSE)
Expand All @@ -397,3 +362,55 @@ footnote_non_binding <- function(x_alpha, full_alpha) {
"(", a2, " - ", a3, " = ", a1, ") ", "under the null hypothesis."
)
}

# where to add the non-binding design footnote
gsd_footnote_row <- function(x, bound) {
# for a vector of "Analysis: N", get a logical vector `i`, in which `TRUE`
# indicates the position of the largest `N`
a <- x$Analysis
r <- "^Analysis: ([0-9]+).*"
i <- grepl(r, a)
k <- as.numeric(sub(r, '\\1', a[i]))
i[i] <- k == max(k)
i & x$Bound == bound
}

# a list of information for `as_[gt|rtf].gs_design()` methods: the transformed
# data, title, and footnote, etc.
gsd_parts <- function(
x, title, subtitle, spannersub, footnote, bound, columns, inf_bound,
alpha_column = spannersub[2], transform = identity
jdblischak marked this conversation as resolved.
Show resolved Hide resolved
) {
method <- intersect(c("ahr", "wlr", "combo", "rd"), class(x))[1]
if (!inf_bound) x <- filter(x, !is.infinite(Z))
# `x` needs a custom transformation in as_rtf()
x2 <- transform(x)

columns <- gsd_columns(columns, method, x2)
x2 <- x2[, columns]
x2 <- subset(x2, !is.na(`Alternate hypothesis`) & !is.na(`Null hypothesis`))
x2 <- subset(x2, Bound %in% bound)

i <- match(c("Alternate hypothesis", "Null hypothesis"), names(x2))
names(x2)[i] <- spannersub

title <- title %||% paste("Bound summary", switch(
method,
ahr = "for AHR design", wlr = "for WLR design",
combo = "for MaxCombo design", rd = "of Binary Endpoint"
))
subtitle <- subtitle %||% switch(
method,
ahr = "AHR approximations of ~HR at bound",
wlr = "WLR approximation of ~wHR at bound",
combo = "MaxCombo approximation",
rd = "measured by risk difference"
)

list(
x = dplyr::arrange(x2, Analysis),
title = title, subtitle = subtitle,
footnote = footnote %||% gsd_footnote(method, columns),
alpha = max(filter(x, Bound == bound[1])[[alpha_column]])
)
}
Loading