-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding model construction helpers (#121)
**What changes are proposed in this pull request?** * Adding functions to assist in constructing models `construct_model()`, `reformulate2()` (similar to `stats::reformulate()` but adds backticks around variable names with spaces in them), `bt()` (adds backtics to variable as needed), and `bt_strip()` (removes backticks from character variable names). I also updated `ard_survey_svychisq()` to use `reformulate2()` instead of `stats::reformulate()` which allows variable names with spaces in them. Other instances of `stats::reformulate()` should also be updated in the future. The reason we're adding the constructors is because the full ARS (Analysis Result Standard) requires that we begin with a data frame, and these help us take a data frame, a response variable, and covariates to construct complex models. **Reference GitHub issue associated with pull request.** _e.g., 'closes #<issue number>'_ closes #108 -------------------------------------------------------------------------------- Pre-review Checklist (if item does not apply, mark is as complete) - [x] **All** GitHub Action workflows pass with a ✅ - [x] PR branch has pulled the most recent updates from master branch: `usethis::pr_merge_main()` - [x] If a bug was fixed, a unit test was added. - [x] If a new `ard_*()` function was added, it passes the ARD structural checks from `cards::check_ard_structure()`. - [x] Code coverage is suitable for any new functions/features (generally, 100% coverage for new code): `devtools::test_coverage()` Reviewer Checklist (if item does not apply, mark is as complete) - [ ] If a bug was fixed, a unit test was added. - [ ] Code coverage is suitable for any new functions/features: `devtools::test_coverage()` When the branch is ready to be merged: - [ ] Update `NEWS.md` with the changes from this pull request under the heading "`# cardx (development version)`". If there is an issue associated with the pull request, reference it in parentheses at the end update (see `NEWS.md` for examples). - [ ] **All** GitHub Action workflows pass with a ✅ - [ ] Approve Pull Request - [ ] Merge the PR. Please use "Squash and merge" or "Rebase and merge". --------- Co-authored-by: Davide Garolini <[email protected]>
- Loading branch information
Showing
12 changed files
with
390 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
#' Construction Helpers | ||
#' | ||
#' These functions help construct calls to various types of models. | ||
#' | ||
#' - `construct_model()`: Builds models of the form `method(data = data, formula = formula, method.args!!!)`. | ||
#' If the `package` argument is specified, that package is temporarily attached | ||
#' when the model is evaluated. | ||
#' | ||
#' - `reformulate2()`: This is a copy of `reformulate()` except that variable | ||
#' names that contain a space are wrapped in backticks. | ||
#' | ||
#' - `bt()`: Adds backticks to a character vector. | ||
#' | ||
#' - `bt_strip()`: Removes backticks from a string if it begins and ends with a backtick. | ||
#' | ||
#' @param x (`data.frame`)\cr | ||
#' a data frame | ||
#' @param formula (`formula`)\cr | ||
#' a formula | ||
#' @param method (`string`)\cr | ||
#' string naming the function to be called, e.g. `"glm"`. | ||
#' If function belongs to a library that is not attached, the package name | ||
#' must be specified in the `package` argument. | ||
#' @param method.args (named `list`)\cr | ||
#' named list of arguments that will be passed to `fn`. | ||
#' @param package (`string`)\cr | ||
#' string of package name that will be temporarily loaded when function | ||
#' specified in `method` is executed. | ||
#' @param x (`character`)\cr | ||
#' character vector, typically of variable names | ||
#' @param pattern (`string`)\cr | ||
#' regular expression string. If the regex matches, backticks are added | ||
#' to the string. When `NULL`, backticks are not added. | ||
#' @param pattern_term,pattern_response passed to `bt(pattern)` for arguments | ||
#' `stats::reformulate(termlabels, response)`. | ||
#' @inheritParams rlang::eval_tidy | ||
#' @inheritParams stats::reformulate | ||
#' @inheritParams rlang::args_dots_empty | ||
#' | ||
#' @return depends on the calling function | ||
#' @name construction_helpers | ||
#' | ||
#' @examplesIf do.call(asNamespace("cardx")$is_pkg_installed, list(pkg = c("withr", "lme4"), reference_pkg = "cardx")) | ||
#' construct_model( | ||
#' x = mtcars, | ||
#' formula = am ~ mpg + (1 | vs), | ||
#' method = "glmer", | ||
#' method.args = list(family = binomial), | ||
#' package = "lme4" | ||
#' ) | ||
#' | ||
#' construct_model( | ||
#' x = mtcars |> dplyr::rename(`M P G` = mpg), | ||
#' formula = reformulate2(c("M P G", "cyl"), response = "hp"), | ||
#' method = "lm" | ||
#' ) |> | ||
#' ard_regression() |> | ||
#' dplyr::filter(stat_name %in% c("term", "estimate", "p.value")) | ||
NULL | ||
|
||
#' @rdname construction_helpers | ||
#' @export | ||
construct_model <- function(x, ...) { | ||
UseMethod("construct_model") | ||
} | ||
|
||
#' @rdname construction_helpers | ||
#' @export | ||
construct_model.data.frame <- function(x, formula, method, method.args = list(), package = "base", env = caller_env(), ...) { | ||
set_cli_abort_call() | ||
# check pkg installations ---------------------------------------------------- | ||
check_dots_empty() | ||
check_pkg_installed(c("withr", package), reference_pkg = "cardx") | ||
|
||
check_not_missing(formula) | ||
check_class(formula, cls = "formula") | ||
|
||
check_not_missing(method) | ||
check_string(method) | ||
check_not_namespaced(method) | ||
|
||
# convert method.args to list of expressions (to account for NSE inputs) ----- | ||
method.args <- call_args(enexpr(method.args)) | ||
|
||
# build model ---------------------------------------------------------------- | ||
withr::with_namespace( | ||
package = package, | ||
call2(.fn = method, formula = formula, data = x, !!!method.args) |> | ||
eval_tidy(env = env) | ||
) | ||
} | ||
|
||
#' @rdname construction_helpers | ||
#' @export | ||
reformulate2 <- function(termlabels, response = NULL, intercept = TRUE, | ||
pattern_term = "[ \n\r]", pattern_response = "[ \n\r]", | ||
env = parent.frame()) { | ||
stats::reformulate( | ||
termlabels = bt(termlabels, pattern_term), | ||
response = bt(response, pattern_response), | ||
intercept = intercept, | ||
env = env | ||
) | ||
} | ||
|
||
#' @rdname construction_helpers | ||
#' @export | ||
bt <- function(x, pattern = "[ \n\r]") { | ||
if (is_empty(x)) { | ||
return(x) | ||
} | ||
if (is_empty(pattern)) { | ||
return(x) | ||
} | ||
ifelse( | ||
str_detect(x, pattern = pattern), | ||
paste0("`", x, "`"), | ||
x | ||
) | ||
} | ||
|
||
#' @rdname construction_helpers | ||
#' @export | ||
bt_strip <- function(x) { | ||
ifelse( | ||
str_detect(x, "^`.*`$"), | ||
substr(x, 2, nchar(x) - 1), | ||
x | ||
) | ||
} | ||
|
||
check_not_namespaced <- function(x, | ||
arg_name = rlang::caller_arg(x), | ||
class = "check_not_namespaced", | ||
call = get_cli_abort_call()) { | ||
check_string(x, arg_name = arg_name, call = call, class = "check_not_namespaced") | ||
|
||
if (str_detect(x, "::")) { | ||
c("Argument {.arg {arg_name}} cannot be namespaced.", | ||
i = "Put the package name in the {.arg package} argument." | ||
) |> | ||
cli::cli_abort(call = call, class = class) | ||
} | ||
|
||
invisible(x) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,3 +57,4 @@ reference: | |
- title: "Helpers" | ||
- contents: | ||
- proportion_ci | ||
- construction_helpers |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.