Skip to content

Commit d01c6a0

Browse files
ddsjobergedelarua
andauthored
Adding ard_stats_anova() (#107)
**What changes are proposed in this pull request?** - `ard_stats_anova()` for calculating ANOVA results using `stats::anova()`. (#12) There are two implementations of this function via S3 methods. 1. The first is when a user calculates the `anova()` object and passes it to `ard_stats_anova()`. 2. The second is when a user passes a data frame. We use that data frame and information from the other arguments to construct the models and pass the models to `anova()` **Reference GitHub issue associated with pull request.** _e.g., 'closes #<issue number>'_ closes #12 -------------------------------------------------------------------------------- Pre-review Checklist (if item does not apply, mark is as complete) - [ ] **All** GitHub Action workflows pass with a ✅ - [ ] PR branch has pulled the most recent updates from master branch: `usethis::pr_merge_main()` - [ ] If a bug was fixed, a unit test was added. - [ ] If a new `ard_*()` function was added, it passes the ARD structural checks from `cards::check_ard_structure()`. - [ ] Code coverage is suitable for any new functions/features (generally, 100% coverage for new code): `devtools::test_coverage()` - [ ] Request a reviewer Reviewer Checklist (if item does not apply, mark is as complete) - [x] If a bug was fixed, a unit test was added. - [x] Run `pkgdown::build_site()`. Check the R console for errors, and review the rendered website. - [x] 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 "`# cards (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". --------- Signed-off-by: Daniel Sjoberg <danield.sjoberg@gmail.com> Co-authored-by: Emily de la Rua <59304861+edelarua@users.noreply.github.com>
1 parent 452faa6 commit d01c6a0

File tree

9 files changed

+459
-2
lines changed

9 files changed

+459
-2
lines changed

DESCRIPTION

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,15 @@ Suggests:
3030
broom.helpers (>= 1.13.0),
3131
car (>= 3.0-11),
3232
effectsize (>= 0.6.0),
33+
geepack (>= 1.3.2),
34+
lme4 (>= 1.1-31),
3335
parameters (>= 0.20.2),
3436
smd (>= 0.6.6),
3537
spelling,
3638
survey (>= 4.1),
3739
survival (>= 3.2-11),
3840
testthat (>= 3.2.0),
39-
withr
41+
withr (>= 2.5.0)
4042
Remotes:
4143
insightsengineering/cards
4244
Config/Needs/website: insightsengineering/nesttemplate

NAMESPACE

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Generated by roxygen2: do not edit by hand
22

33
S3method(ard_regression,default)
4+
S3method(ard_stats_anova,anova)
5+
S3method(ard_stats_anova,data.frame)
46
export("%>%")
57
export(all_of)
68
export(any_of)
@@ -15,6 +17,7 @@ export(ard_proportion_ci)
1517
export(ard_regression)
1618
export(ard_regression_basic)
1719
export(ard_smd_smd)
20+
export(ard_stats_anova)
1821
export(ard_stats_aov)
1922
export(ard_stats_chisq_test)
2023
export(ard_stats_fisher_test)
@@ -49,6 +52,7 @@ export(starts_with)
4952
export(where)
5053
import(rlang)
5154
importFrom(dplyr,"%>%")
55+
importFrom(dplyr,across)
5256
importFrom(dplyr,all_of)
5357
importFrom(dplyr,any_of)
5458
importFrom(dplyr,contains)

NEWS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ ard_moodtest() -> ard_stats_mood_test()
2020

2121
* Added the following functions for calculating Analysis Results Data (ARD).
2222
- `ard_stats_aov()` for calculating ANOVA results using `stats::aov()`. (#3)
23-
- `ard_aod_wald_test()` for calculating Wald Tests for regression models using `aod::wald.test()`. (#3)
23+
- `ard_stats_anova()` for calculating ANOVA results using `stats::anova()`. (#12)
24+
- `ard_aod_wald_test()` for calculating Wald Tests for regression models using `aod::wald.test()`. (#84)
2425
- `ard_car_anova()` for calculating ANOVA results using `car::Anova()`. (#3)
2526
- `ard_onewaytest()` for calculating ANOVA results using `stats::oneway.test()`. (#3)
2627
- `ard_cohens_d()`, `ard_paired_cohens_d()`, `ard_hedges_g()`, and `ard_paired_hedges_g()` for standardized differences using `effectsize::cohens_d()` and `effectsize::hedges_g()`. (#50)

R/ard_stats_anova.R

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#' ARD ANOVA
2+
#'
3+
#' Prepare ANOVA results from the `stats::anova()` function.
4+
#' Users may pass a pre-calculated `stats::anova()` object or a list of
5+
#' formulas. In the latter case, the models will be constructed using the
6+
#' information passed and models will be passed to `stats::anova()`.
7+
#'
8+
#' @param x (`anova` or `data.frame`)\cr
9+
#' an object of class `'anova'` created with `stats::anova()` or
10+
#' a data frame
11+
#' @param formulas (`list`)\cr
12+
#' a list of formulas
13+
#' @param fn (`string`)\cr
14+
#' string naming the function to be called, e.g. `"glm"`.
15+
#' If function belongs to a library that is not attached, the package name
16+
#' must be specified in the `package` argument.
17+
#' @param fn.args (named `list`)\cr
18+
#' named list of arguments that will be passed to `fn`.
19+
#' @param package (`string`)\cr
20+
#' string of package name that will be temporarily loaded when function
21+
#' specified in `method` is executed.
22+
#' @param method (`string`)\cr
23+
#' string of the method used. Default is `"ANOVA results from `stats::anova()`"`.
24+
#' We provide the option to change this as `stats::anova()` can produce
25+
#' results from many types of models that may warrant a more precise
26+
#' description.
27+
#' @inheritParams rlang::args_dots_empty
28+
#'
29+
#' @details
30+
#' When a list of formulas is supplied to `ard_stats_anova()`, these formulas
31+
#' along with information from other arguments, are used to construct models
32+
#' and pass those models to `stats::anova()`.
33+
#'
34+
#' The models are constructed using `rlang::exec()`, which is similar to `do.call()`.
35+
#'
36+
#' ```r
37+
#' rlang::exec(.fn = fn, formula = formula, data = data, !!!fn.args)
38+
#' ```
39+
#'
40+
#' The above function is executed in `withr::with_namespace(package)`, which
41+
#' allows for the use of `ard_stats_anova(fn)` from packages,
42+
#' e.g. `package = 'lme4'` must be specified when `fn = 'glmer'`.
43+
#' See example below.
44+
#'
45+
#' @return ARD data frame
46+
#' @name ard_stats_anova
47+
#'
48+
#' @examplesIf cards::is_pkg_installed(c("broom", "withr", "lme4"), reference_pkg = "cardx")
49+
#' anova(
50+
#' lm(mpg ~ am, mtcars),
51+
#' lm(mpg ~ am + hp, mtcars)
52+
#' ) |>
53+
#' ard_stats_anova()
54+
#'
55+
#' ard_stats_anova(
56+
#' x = mtcars,
57+
#' formulas = list(am ~ mpg, am ~ mpg + hp),
58+
#' fn = "glm",
59+
#' fn.args = list(family = binomial)
60+
#' )
61+
#'
62+
#' ard_stats_anova(
63+
#' x = mtcars,
64+
#' formulas = list(am ~ 1 + (1 | vs), am ~ mpg + (1 | vs)),
65+
#' fn = "glmer",
66+
#' fn.args = list(family = binomial),
67+
#' package = "lme4"
68+
#' )
69+
NULL
70+
71+
#' @rdname ard_stats_anova
72+
#' @export
73+
ard_stats_anova <- function(x, ...) {
74+
UseMethod("ard_stats_anova")
75+
}
76+
77+
#' @rdname ard_stats_anova
78+
#' @export
79+
ard_stats_anova.anova <- function(x, method = "ANOVA results from `stats::anova()`", ...) {
80+
# check inputs ---------------------------------------------------------------
81+
check_dots_empty()
82+
cards::check_pkg_installed("broom", reference_pkg = "cardx")
83+
check_string(method, message = "Argument {.arg method} must be a string of a function name.")
84+
85+
# return df in cards formats -------------------------------------------------
86+
lst_results <-
87+
cards::eval_capture_conditions(
88+
.anova_tidy_and_reshape(x, method = method)
89+
)
90+
91+
# final tidying up of cards data frame ---------------------------------------
92+
.anova_final_ard_prep(lst_results, method = method)
93+
}
94+
95+
96+
#' @rdname ard_stats_anova
97+
#' @export
98+
ard_stats_anova.data.frame <- function(x,
99+
formulas,
100+
fn,
101+
fn.args = list(),
102+
package = "base",
103+
method = "ANOVA results from `stats::anova()`",
104+
...) {
105+
# check inputs ---------------------------------------------------------------
106+
check_dots_empty()
107+
check_string(package)
108+
cards::check_pkg_installed(c("broom", "withr", package), reference_pkg = "cardx")
109+
check_not_missing(formulas)
110+
check_not_missing(x)
111+
check_not_missing(fn)
112+
check_string(method, message = "Argument {.arg method} must be a string of a function name.")
113+
check_data_frame(x)
114+
check_string(fn)
115+
if (str_detect(fn, "::")) {
116+
cli::cli_abort(c(
117+
"Argument {.arg fn} cannot be namespaced.",
118+
i = "Put the package name in the {.arg package} argument."
119+
))
120+
}
121+
122+
# calculate results and return df in cards formats ---------------------------
123+
# process fn.args argument
124+
fn.args <- rlang::call_args(rlang::enexpr(fn.args))
125+
126+
# create models
127+
lst_results <-
128+
cards::eval_capture_conditions({
129+
# first build the models
130+
models <-
131+
lapply(
132+
formulas,
133+
function(formula) {
134+
withr::with_namespace(
135+
package = package,
136+
call2(.fn = fn, formula = formula, data = x, !!!fn.args) |>
137+
eval_tidy()
138+
)
139+
}
140+
)
141+
142+
# now calculate `stats::anova()` and reshape results
143+
rlang::inject(stats::anova(!!!models)) |>
144+
.anova_tidy_and_reshape(method = method)
145+
})
146+
147+
# final tidying up of cards data frame ---------------------------------------
148+
.anova_final_ard_prep(lst_results, method = method)
149+
}
150+
151+
.anova_tidy_and_reshape <- function(x, method) {
152+
broom::tidy(x) |>
153+
dplyr::mutate(
154+
across(everything(), as.list),
155+
variable = paste0("model_", dplyr::row_number())
156+
) |>
157+
tidyr::pivot_longer(
158+
cols = -"variable",
159+
names_to = "stat_name",
160+
values_to = "stat"
161+
) |>
162+
dplyr::filter(!is.na(.data$stat)) %>%
163+
# add one more row with the method
164+
{
165+
dplyr::bind_rows(
166+
.,
167+
dplyr::filter(., dplyr::n() == dplyr::row_number()) |>
168+
dplyr::mutate(
169+
stat_name = "method",
170+
stat = list(.env$method)
171+
)
172+
)
173+
}
174+
}
175+
176+
.anova_final_ard_prep <- function(lst_results, method) {
177+
# saving the results in data frame -------------------------------------------
178+
df_card <-
179+
if (!is.null(lst_results[["result"]])) {
180+
lst_results[["result"]]
181+
} else { # if there was an error return a shell of an ARD data frame
182+
dplyr::tibble(
183+
variable = "model_1",
184+
stat_name = c("p.value", "method"),
185+
stat = list(NULL, method)
186+
)
187+
}
188+
189+
# final tidying up of cards data frame ---------------------------------------
190+
df_card |>
191+
dplyr::mutate(
192+
warning = lst_results["warning"],
193+
error = lst_results["error"],
194+
context = "stats_anova",
195+
fmt_fn = lapply(
196+
.data$stat,
197+
function(x) {
198+
switch(is.integer(x),
199+
0L
200+
) %||% switch(is.numeric(x),
201+
1L
202+
)
203+
}
204+
),
205+
stat_label =
206+
dplyr::case_when(
207+
.data$stat_name %in% "p.value" ~ "p-value",
208+
.data$stat_name %in% "sumsq" ~ "Sum of Squares",
209+
.data$stat_name %in% "rss" ~ "Residual Sum of Squares",
210+
.data$stat_name %in% "df" ~ "Degrees of Freedom",
211+
.data$stat_name %in% "df.residual" ~ "df for residuals",
212+
.default = .data$stat_name
213+
)
214+
) |>
215+
cards::tidy_ard_column_order() %>%
216+
{structure(., class = c("card", class(.)))} # styler: off
217+
}

R/cardx-package.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#' @keywords internal
22
#' @import rlang
3+
#' @importFrom dplyr across
34
"_PACKAGE"
45

56
## usethis namespace: start

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ reference:
2626
- contents:
2727
- ard_aod_wald_test
2828
- ard_car_anova
29+
- ard_stats_anova
2930
- ard_stats_aov
3031
- ard_stats_chisq_test
3132
- ard_stats_fisher_test

inst/WORDLIST

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ VIF
1717
XG
1818
Xin
1919
agresti
20+
anova
2021
clopper
2122
coull
2223
de
2324
deff
2425
funder
2526
jeffreys
2627
pearson
28+
pre
2729
sd
2830
strat
2931
vif

man/ard_stats_anova.Rd

Lines changed: 98 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)