Skip to content

Commit 4f4c357

Browse files
elinwetiennebacher
andauthored
Add metrics parameter to data_tabulate() (#689)
Co-authored-by: Etienne Bacher <52219252+etiennebacher@users.noreply.github.com> Co-authored-by: etiennebacher <etienne.bacher@protonmail.com>
1 parent c7bc598 commit 4f4c357

5 files changed

Lines changed: 210 additions & 13 deletions

File tree

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ CHANGES
2323
`.rds`, and `.rdata`). Data encryption is based on the AES-GCM algorithm using
2424
the `openssl::aes_gcm_encrypt()` function (#675).
2525

26+
* `data_tabulate()` gain a `metrics` argument to allow selection of columns to
27+
display ("N", "raw", "valid", and "cumulative") (#689, @elinw).
28+
2629
FIXES
2730

2831
* Fix a test due to R-devel change (#677).

R/data_tabulate.R

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@
3939
#' printed as markdown or HTML table, depending on the environment. See
4040
#' [`insight::export_table()`] for details.
4141
#' @param verbose Toggle warnings and messages.
42+
#' @param metrics Character vector, indicating the types of metrics to be
43+
#' included. Only applies to frequencies, i.e. when `by` is `NULL`. Can include
44+
#' any combination of `N` (frequencies including `NA`), `"raw"` (percentage
45+
#' including `NA` values), `"valid"` (percentage excluding `NA` values) and
46+
#' `"cumulative"` (percentage excluding `NA` values).
4247
#' @param ... not used.
4348
#' @inheritParams extract_column_names
4449
#'
@@ -86,6 +91,9 @@
8691
#' # drop missing values
8792
#' data_tabulate(efc$c172code, remove_na = TRUE)
8893
#'
94+
#' # exclude the cumulative percent column
95+
#' data_tabulate(efc$c172code, metrics = c("raw", "valid"))
96+
#'
8997
#' # data frame
9098
#' data_tabulate(efc, c("e42dep", "c172code"))
9199
#'
@@ -154,6 +162,7 @@ data_tabulate.default <- function(
154162
proportions = NULL,
155163
name = NULL,
156164
verbose = TRUE,
165+
metrics = c("N", "raw", "valid", "cumulative"),
157166
...
158167
) {
159168
# save label attribute, before it gets lost...
@@ -260,7 +269,21 @@ data_tabulate.default <- function(
260269
out$N <- round(out$N)
261270
}
262271

263-
out$`Raw %` <- 100 * out$N / sum(out$N)
272+
# validate "metrics"
273+
if (!is.null(metrics)) {
274+
# match.arg(all.ok = "all") is only valid for R > 4.6, and
275+
# match.arg(all.ok = TRUE) doesn't error if some values are wrong.
276+
invalid <- setdiff(metrics, c("N", "raw", "valid", "cumulative"))
277+
if (length(invalid) > 0) {
278+
insight::format_error(
279+
"Invalid values in `metrics`: ",
280+
text_concatenate(invalid, enclose = '"')
281+
)
282+
}
283+
}
284+
if ("raw" %in% metrics) {
285+
out$`Raw %` <- 100 * out$N / sum(out$N)
286+
}
264287
# if we have missing values, we add a row with NA
265288
if (remove_na) {
266289
out$`Valid %` <- 100 * out$N / sum(out$N)
@@ -269,8 +292,13 @@ data_tabulate.default <- function(
269292
out$`Valid %` <- c(100 * out$N[-nrow(out)] / sum(out$N[-nrow(out)]), NA)
270293
valid_n <- sum(out$N[-length(out$N)], na.rm = TRUE)
271294
}
272-
out$`Cumulative %` <- cumsum(out$`Valid %`)
273295

296+
if ("cumulative" %in% metrics) {
297+
out$`Cumulative %` <- cumsum(out$`Valid %`)
298+
}
299+
if (!"valid" %in% metrics) {
300+
out$`Valid %` <- NULL
301+
}
274302
# add information about variable/group names
275303
if (!is.null(obj_name)) {
276304
if (is.null(group_variable)) {
@@ -286,6 +314,10 @@ data_tabulate.default <- function(
286314
}
287315
out <- cbind(var_info, out)
288316
}
317+
total_n <- sum(out$N, na.rm = TRUE)
318+
if (!"N" %in% metrics) {
319+
out <- data_select(out, exclude = "N")
320+
}
289321

290322
# save information
291323
attr(out, "type") <- .variable_type(x)
@@ -296,7 +328,7 @@ data_tabulate.default <- function(
296328
attr(out, "duplicate_varnames") <- duplicated(out$Variable)
297329
attr(out, "weights") <- weights
298330

299-
attr(out, "total_n") <- sum(out$N, na.rm = TRUE)
331+
attr(out, "total_n") <- total_n
300332
attr(out, "valid_n") <- valid_n
301333
if (is.null(by)) {
302334
attr(out, "by") <- NULL
@@ -344,10 +376,6 @@ data_tabulate.data.frame <- function(
344376
verbose = verbose
345377
)
346378

347-
if (!is.null(by)) {
348-
attr(x, "by") <- by_name
349-
}
350-
351379
# validate "by"
352380
by <- .validate_by(by, x)
353381
# validate "weights"
@@ -875,19 +903,21 @@ format.datawizard_table <- function(x, format = "text", big_mark = NULL, ...) {
875903
# convert to character manually, else, for large numbers,
876904
# format_table() returns scientific notation
877905
x <- as.data.frame(x)
878-
x$N <- as.character(x$N)
879-
906+
if (!is.null(x$N)) {
907+
x$N <- as.character(x$N)
908+
}
880909
# format data frame
881910
ftab <- insight::format_table(x, ...)
882911
ftab[] <- lapply(ftab, function(i) {
883912
i[i == ""] <- ifelse(identical(format, "text"), "<NA>", "(NA)") # nolint
884913
i
885914
})
886-
ftab$N <- gsub("\\.00$", "", ftab$N)
887-
888-
# insert big marks?
889-
ftab$N <- .add_commas_in_numbers(ftab$N, big_mark)
915+
if (!is.null(ftab$N)) {
916+
ftab$N <- gsub("\\.00$", "", ftab$N)
890917

918+
# insert big marks?
919+
ftab$N <- .add_commas_in_numbers(ftab$N, big_mark)
920+
}
891921
ftab
892922
}
893923

man/data_tabulate.Rd

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

tests/testthat/_snaps/data_tabulate.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,24 @@
161161
| | (NA)| 0| 0.00| (NA)| (NA)|
162162
| | | | | | |
163163

164+
# data_tabulate data.frame with wrong value for 'metrics'
165+
166+
Code
167+
data_tabulate(efc, c("e16sex", "c172code"), metrics = "foo")
168+
Condition
169+
Error:
170+
! Invalid values in `metrics`:
171+
"foo"
172+
173+
---
174+
175+
Code
176+
data_tabulate(efc, c("e16sex", "c172code"), metrics = c("N", "foo"))
177+
Condition
178+
Error:
179+
! Invalid values in `metrics`:
180+
"foo"
181+
164182
# data_tabulate print
165183

166184
Code

tests/testthat/test-data_tabulate.R

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,141 @@ test_that("data_tabulate data.frame", {
181181
)
182182
})
183183

184+
185+
test_that("data_tabulate data.frame with metrics", {
186+
data(efc, package = "datawizard")
187+
x <- data_tabulate(efc, c("e16sex", "c172code"), metrics = "raw")
188+
expect_s3_class(x, "list")
189+
expect_length(x, 2L)
190+
expect_identical(
191+
attributes(x[[1]]),
192+
list(
193+
names = c(
194+
"Variable",
195+
"Value",
196+
"Raw %"
197+
),
198+
row.names = 1:3,
199+
class = c("datawizard_table", "data.frame"),
200+
type = "numeric",
201+
varname = "e16sex",
202+
label = "elder's gender",
203+
object = "e16sex",
204+
duplicate_varnames = c(FALSE, TRUE, TRUE),
205+
total_n = 100L,
206+
valid_n = 100L
207+
)
208+
)
209+
expect_identical(
210+
attributes(x[[2]]),
211+
list(
212+
names = c(
213+
"Variable",
214+
"Value",
215+
"Raw %"
216+
),
217+
row.names = 1:4,
218+
class = c("datawizard_table", "data.frame"),
219+
type = "numeric",
220+
varname = "c172code",
221+
label = "carer's level of education",
222+
object = "c172code",
223+
duplicate_varnames = c(FALSE, TRUE, TRUE, TRUE),
224+
total_n = 100L,
225+
valid_n = 90L
226+
)
227+
)
228+
table1 <- x[[1]]
229+
expect_identical(
230+
as.vector(table1$Value),
231+
as.character(c(
232+
sort(
233+
unique(efc$e16sex)
234+
),
235+
NA
236+
))
237+
)
238+
expect_null(table1$N)
239+
expect_identical(
240+
table1$`Raw %`,
241+
as.vector(
242+
100 * table(efc$e16sex, useNA = "always") / length(efc$e16sex)
243+
),
244+
ignore_attr = TRUE,
245+
tolerance = 1e-3
246+
)
247+
})
248+
249+
test_that("data_tabulate data.frame with metrics = NULL", {
250+
# this is equivalent to metrics = c()
251+
data(efc, package = "datawizard")
252+
x <- data_tabulate(efc, c("e16sex", "c172code"), metrics = NULL)
253+
expect_s3_class(x, "list")
254+
expect_length(x, 2L)
255+
expect_identical(
256+
attributes(x[[1]]),
257+
list(
258+
names = c(
259+
"Variable",
260+
"Value"
261+
),
262+
row.names = 1:3,
263+
class = c("datawizard_table", "data.frame"),
264+
type = "numeric",
265+
varname = "e16sex",
266+
label = "elder's gender",
267+
object = "e16sex",
268+
duplicate_varnames = c(FALSE, TRUE, TRUE),
269+
total_n = 100L,
270+
valid_n = 100L
271+
)
272+
)
273+
expect_identical(
274+
attributes(x[[2]]),
275+
list(
276+
names = c(
277+
"Variable",
278+
"Value"
279+
),
280+
row.names = 1:4,
281+
class = c("datawizard_table", "data.frame"),
282+
type = "numeric",
283+
varname = "c172code",
284+
label = "carer's level of education",
285+
object = "c172code",
286+
duplicate_varnames = c(FALSE, TRUE, TRUE, TRUE),
287+
total_n = 100L,
288+
valid_n = 90L
289+
)
290+
)
291+
table1 <- x[[1]]
292+
expect_identical(
293+
as.vector(table1$Value),
294+
as.character(c(
295+
sort(
296+
unique(efc$e16sex)
297+
),
298+
NA
299+
))
300+
)
301+
expect_null(table1$N)
302+
expect_null(
303+
table1$`Raw %`
304+
)
305+
})
306+
307+
test_that("data_tabulate data.frame with wrong value for 'metrics'", {
308+
data(efc, package = "datawizard")
309+
expect_snapshot(
310+
data_tabulate(efc, c("e16sex", "c172code"), metrics = "foo"),
311+
error = TRUE
312+
)
313+
expect_snapshot(
314+
data_tabulate(efc, c("e16sex", "c172code"), metrics = c("N", "foo")),
315+
error = TRUE
316+
)
317+
})
318+
184319
test_that("data_tabulate data.frame by", {
185320
data(efc, package = "datawizard")
186321
x <- data_tabulate(efc, "c172code", by = "e16sex")
@@ -228,6 +363,7 @@ test_that("data_tabulate default by", {
228363
)
229364
)
230365
})
366+
231367
test_that("data_tabulate grouped data.frame by", {
232368
skip_if_not_installed("poorman")
233369
data(efc, package = "datawizard")

0 commit comments

Comments
 (0)