From 9c30fda5f98e852d502f08da578c73071852e3be Mon Sep 17 00:00:00 2001 From: hcirellu Date: Sat, 31 Jan 2026 21:43:06 +0100 Subject: [PATCH 1/4] coerce integer64 to factor in table for multiple inputs --- NEWS.md | 1 + R/highlevel64.R | 14 +++++++++----- man/table.Rd | 4 ++-- tests/testthat/test-highlevel64.R | 8 ++++++++ 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/NEWS.md b/NEWS.md index 570fb462..ffd4f331 100644 --- a/NEWS.md +++ b/NEWS.md @@ -61,6 +61,7 @@ - Ignores leading/trailing whitespace (as does `as.integer()`; #232). 1. `sortcache`, `sortordercache` and `ordercache` get a new argument `na.last`. 1. `matrix`, `array`, `%*%` and `as.matrix` get an `integer64` method (#45). Thanks @hcirellu. +1. The result of `table` with multiple inputs including `integer64` is now ordered according to `integer64` values for the corresponding input (#236). Thanks @hcirellu. ## BUG FIXES diff --git a/R/highlevel64.R b/R/highlevel64.R index 1f5845be..06ba7af3 100644 --- a/R/highlevel64.R +++ b/R/highlevel64.R @@ -2046,8 +2046,6 @@ table = function(..., exclude=if (useNA == "no") c(NA, NaN), useNA=c("no", "ifan dots = list(...) is_int64 = vapply(dots, is.integer64, logical(1L), USE.NAMES=FALSE) is_int = vapply(dots, is.integer, logical(1L), USE.NAMES=FALSE) - # TODO(#236): avoid this workaround to hack S3 dispatch. For now, - # we only use table.integer64() when we are sure there is no information loss (coercion). if (length(dots) && any(is_int64) && all(is_int64 | is_int)) { sys_call = sys.call() sys_call[[1L]] = table.integer64 @@ -2119,9 +2117,15 @@ table.integer64 = function(..., if (!N) stop("nothing to tabulate", domain="R-base") - # table(as.integer64(1L), "a") is dispatched to table.integer64, but should be handled by table.default - if (!all(vapply(seq_len(N), function(ii) {el = A(ii); is.integer64(el) || is.integer(el)}, logical(1L)))) - return(NextMethod()) + # table(as.integer64(1L), "a") is dispatched to table.integer64, but should be handled by table.default with integer64 already as factor + if (!all(vapply(seq_len(N), function(ii) {el = A(ii); is.integer64(el) || is.integer(el)}, logical(1L)))) { + useNA = match.arg(useNA) + ret = withCallingHandlers_and_choose_call( + do.call("table", c(lapply(seq_len(N), function(ii) {val = A(ii); if (is.integer64(val)) factor(val, exclude=NULL) else val}), list(exclude=exclude, useNA=useNA, dnn=dnn, deparse.level=deparse.level))), + c("table", "table.integer64") + ) + return(ret) + } if (N == 1L && is.list(A(1L))) { args = A(1L) # nolint: object_overwrite_linter. This code should probably be refactored anyway. diff --git a/man/table.Rd b/man/table.Rd index e370784d..dc2617bf 100644 --- a/man/table.Rd +++ b/man/table.Rd @@ -90,8 +90,8 @@ input dimensions contingency table of the counts at each combination of vector values. } \details{ -If at least one argument of \code{...} is integer64 and the remaining arguments of \code{...} -are integer64 or integer the `table.integer64` method is used. Only this method +If at least one argument of \code{...} is integer64 and the remaining arguments of \code{...} +are integer64 or integer the \code{table.integer64} method is used. Only this method supports the arguments \code{return}, \code{order}, \code{nunique}, and \code{method}. This function automatically chooses from several low-level functions considering diff --git a/tests/testthat/test-highlevel64.R b/tests/testthat/test-highlevel64.R index 37814dab..47730ab1 100644 --- a/tests/testthat/test-highlevel64.R +++ b/tests/testthat/test-highlevel64.R @@ -522,3 +522,11 @@ test_that("table dispatch integer64 and 'higher' types and factors", { expect_identical(table(as.integer64(1L), 1.0+1.0i), table(1L, 1.0+1.0i)) expect_identical(table(1.0+1.0i, as.integer64(1L)), table(1.0+1.0i, 1L)) }) + +test_that("table dispatch to default with integer64 correctly coerced to factor", { + x = c(132724613L, -2143220989L, -1L, NA, 1L) + y = c(TRUE, FALSE) + expect_identical(table(x=as.integer64(x), rep_len(y, length(x))), table(x, rep_len(y, length(x)))) + expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), useNA="ifany"), table(x, rep_len(y, length(x)), useNA="ifany")) + expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), exclude=NULL), table(x, rep_len(y, length(x)), exclude=NULL)) +}) From 54641ab0f7365ad9cba1fe5cd6eed6538c523bc0 Mon Sep 17 00:00:00 2001 From: hcirellu Date: Mon, 2 Feb 2026 09:35:44 +0100 Subject: [PATCH 2/4] deactivate test until PR #255 is merged --- tests/testthat/test-highlevel64.R | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/testthat/test-highlevel64.R b/tests/testthat/test-highlevel64.R index 47730ab1..d43b9649 100644 --- a/tests/testthat/test-highlevel64.R +++ b/tests/testthat/test-highlevel64.R @@ -523,10 +523,11 @@ test_that("table dispatch integer64 and 'higher' types and factors", { expect_identical(table(1.0+1.0i, as.integer64(1L)), table(1.0+1.0i, 1L)) }) -test_that("table dispatch to default with integer64 correctly coerced to factor", { - x = c(132724613L, -2143220989L, -1L, NA, 1L) - y = c(TRUE, FALSE) - expect_identical(table(x=as.integer64(x), rep_len(y, length(x))), table(x, rep_len(y, length(x)))) - expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), useNA="ifany"), table(x, rep_len(y, length(x)), useNA="ifany")) - expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), exclude=NULL), table(x, rep_len(y, length(x)), exclude=NULL)) -}) +# TODO(#255): Activate this test as soon as PR 255 is merged. +# test_that("table dispatch to default with integer64 correctly coerced to factor", { +# x = c(132724613L, -2143220989L, -1L, NA, 1L) +# y = c(TRUE, FALSE) +# expect_identical(table(x=as.integer64(x), rep_len(y, length(x))), table(x, rep_len(y, length(x)))) +# expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), useNA="ifany"), table(x, rep_len(y, length(x)), useNA="ifany")) +# expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), exclude=NULL), table(x, rep_len(y, length(x)), exclude=NULL)) +# }) From 110a59d6ece5546cc5f609ce763db1d772d8ea93 Mon Sep 17 00:00:00 2001 From: hcirellu Date: Mon, 23 Feb 2026 13:50:23 +0100 Subject: [PATCH 3/4] activate test after PR #255 is merged --- tests/testthat/test-highlevel64.R | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/tests/testthat/test-highlevel64.R b/tests/testthat/test-highlevel64.R index d43b9649..47730ab1 100644 --- a/tests/testthat/test-highlevel64.R +++ b/tests/testthat/test-highlevel64.R @@ -523,11 +523,10 @@ test_that("table dispatch integer64 and 'higher' types and factors", { expect_identical(table(1.0+1.0i, as.integer64(1L)), table(1.0+1.0i, 1L)) }) -# TODO(#255): Activate this test as soon as PR 255 is merged. -# test_that("table dispatch to default with integer64 correctly coerced to factor", { -# x = c(132724613L, -2143220989L, -1L, NA, 1L) -# y = c(TRUE, FALSE) -# expect_identical(table(x=as.integer64(x), rep_len(y, length(x))), table(x, rep_len(y, length(x)))) -# expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), useNA="ifany"), table(x, rep_len(y, length(x)), useNA="ifany")) -# expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), exclude=NULL), table(x, rep_len(y, length(x)), exclude=NULL)) -# }) +test_that("table dispatch to default with integer64 correctly coerced to factor", { + x = c(132724613L, -2143220989L, -1L, NA, 1L) + y = c(TRUE, FALSE) + expect_identical(table(x=as.integer64(x), rep_len(y, length(x))), table(x, rep_len(y, length(x)))) + expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), useNA="ifany"), table(x, rep_len(y, length(x)), useNA="ifany")) + expect_identical(table(x=as.integer64(x), rep_len(y, length(x)), exclude=NULL), table(x, rep_len(y, length(x)), exclude=NULL)) +}) From 88150a32de0e0f7630ea6a8089f041050188a093 Mon Sep 17 00:00:00 2001 From: hcirellu Date: Mon, 16 Mar 2026 11:27:48 +0100 Subject: [PATCH 4/4] assure table.integer64 is still called with any integer64 --- R/highlevel64.R | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/R/highlevel64.R b/R/highlevel64.R index 3859b815..6f391123 100644 --- a/R/highlevel64.R +++ b/R/highlevel64.R @@ -2115,7 +2115,6 @@ table = function(..., exclude=if (useNA == "no") c(NA, NaN), useNA=c("no", "ifan else sel = !names(dots) %in% c("return", "order", "nunique", "method") is_int64 = vapply(dots[sel], is.integer64, logical(1L), USE.NAMES=FALSE) - is_int = vapply(dots[sel], is.integer, logical(1L), USE.NAMES=FALSE) sys_call = match.call() sel = which(vapply(sys_call[seq_along(dots) + 1L], is.symbol, FALSE)) + 1L if (length(sel)) { @@ -2137,7 +2136,7 @@ table = function(..., exclude=if (useNA == "no") c(NA, NaN), useNA=c("no", "ifan pf = parent.frame() # add unused function `list.names` to eliminate CMD check NOTE about missing function definition. list.names = function(...) {} - if (length(dots) && any(is_int64) && all(is_int64 | is_int)) { + if (length(dots) && any(is_int64)) { sys_call[[1L]] = table.integer64 withCallingHandlers_and_choose_call(eval(sys_call, envir=pf), c("table", "table.default"), "table.integer64") } else {