From 0cff4c9e91adff91136600c3b1fdddb6e95366ea Mon Sep 17 00:00:00 2001 From: rikivillalba Date: Thu, 22 Oct 2026 20:28:21 -0300 Subject: [PATCH 1/7] rowwiseDT key support --- R/rowwiseDT.R | 14 ++++++++++++++ inst/tests/tests.Rraw | 11 +++++++++++ 2 files changed, 25 insertions(+) diff --git a/R/rowwiseDT.R b/R/rowwiseDT.R index 1451e58498..6b6b81641b 100644 --- a/R/rowwiseDT.R +++ b/R/rowwiseDT.R @@ -2,11 +2,23 @@ rowwiseDT = function(...) { x = substitute(list(...))[-1L] if (is.null(nms <- names(x))) stopf("Must provide at least one column (use `name=`). See ?rowwiseDT for details") + if (length(key_pos <- which(nms == "key" & nzchar(x))) > 0L) { + if (length(key_pos) > 1L) + stopf("key specified more than once") + if (length(key <- eval(x[[key_pos]], parent.frame())) == 1L) + key = cols_from_csv(key) + x = x[-key_pos] + nms = nms[-key_pos] + } else { + key_pos = 0L + } header_pos = which(nzchar(nms)) if (any(nzchar(x[header_pos]))) stopf("Named arguments must be empty") if (!identical(header_pos, seq_along(header_pos))) stopf("Header must be the first N arguments") + if (key_pos && !(key_pos - 1L) %in% c(0L, length(header_pos), length(x))) + stopf("key specification not allowed in between names or data, only boundaries") header = nms[header_pos] ncols = length(header) body = lapply(x[-header_pos], eval, envir = parent.frame()) @@ -27,5 +39,7 @@ rowwiseDT = function(...) { body = split(body, rep(seq_len(nrows), each = ncols)) ans = rbindlist(body) setnames(ans, header) + if (key_pos) + setkeyv(ans, key) ans } diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 4277ed1533..d711753956 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -18843,6 +18843,17 @@ test(2282.08, rowwiseDT(A=,B=,1,2,C=,4), error="Header must be the first N argum # evaluate arguments in the correct frame ncols = 1e6 test(2282.09, rowwiseDT(A=,ncols), data.table(A=ncols)) +# tests involving key specification in rowwiseDT +test(2282.10, rowwiseDT(key="B",A=,B=,1,2), data.table(A=1,B=2,key="B")) +test(2282.11, rowwiseDT(A=,B=,key="B",1,2), data.table(A=1,B=2,key="B")) +test(2282.12, rowwiseDT(A=,B=,1,2,key="B"), data.table(A=1,B=2,key="B")) +test(2282.13, rowwiseDT(A=,key="B",B=,1,2), error = "key specification not allowed in between names or data, only boundaries") +test(2282.14, rowwiseDT(A=,B=,1,key="B",2), error = "key specification not allowed in between names or data, only boundaries") +test(2282.15, rowwiseDT(A=,key=,B=,1,2,3,key="B"), data.table(A=1,B=3,key="B")[,key:=2][,.(A,key,B)]) +test(2282.16, rowwiseDT(key="A",key="B",A=,B=,key=,1,2,3), error = "key specified more than once") +test(2282.17, rowwiseDT(A=,B=,key="A,B",1,2), data.table(A=1,B=2, key="A,B")) +# as "" equals to "missing", key = "" means column name (backwards compatible) +test(2282.18, rowwiseDT(A="",B="",key="",1,2,3), data.table(A=1,B=2)[,key:=3]) # test 2283 moved tests to optimize.Rraw From 8e8b3f559ad8c32d230fb5c21669c19688045116 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 23 Oct 2026 09:59:25 -0700 Subject: [PATCH 2/7] de-lint trailing ws Co-authored-by: Michael Chirico --- R/rowwiseDT.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/rowwiseDT.R b/R/rowwiseDT.R index 6b6b81641b..43d6dcf59f 100644 --- a/R/rowwiseDT.R +++ b/R/rowwiseDT.R @@ -5,7 +5,7 @@ rowwiseDT = function(...) { if (length(key_pos <- which(nms == "key" & nzchar(x))) > 0L) { if (length(key_pos) > 1L) stopf("key specified more than once") - if (length(key <- eval(x[[key_pos]], parent.frame())) == 1L) + if (length(key <- eval(x[[key_pos]], parent.frame())) == 1L) key = cols_from_csv(key) x = x[-key_pos] nms = nms[-key_pos] @@ -39,7 +39,7 @@ rowwiseDT = function(...) { body = split(body, rep(seq_len(nrows), each = ncols)) ans = rbindlist(body) setnames(ans, header) - if (key_pos) + if (key_pos) setkeyv(ans, key) ans } From 002d5eda690ba478eebca57ade116e34f9f70981 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 23 Oct 2026 10:03:24 -0700 Subject: [PATCH 3/7] style in tests, incl. simplifying some expected values with setDT --- inst/tests/tests.Rraw | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index d711753956..ccdec74d63 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -18844,16 +18844,17 @@ test(2282.08, rowwiseDT(A=,B=,1,2,C=,4), error="Header must be the first N argum ncols = 1e6 test(2282.09, rowwiseDT(A=,ncols), data.table(A=ncols)) # tests involving key specification in rowwiseDT -test(2282.10, rowwiseDT(key="B",A=,B=,1,2), data.table(A=1,B=2,key="B")) -test(2282.11, rowwiseDT(A=,B=,key="B",1,2), data.table(A=1,B=2,key="B")) -test(2282.12, rowwiseDT(A=,B=,1,2,key="B"), data.table(A=1,B=2,key="B")) -test(2282.13, rowwiseDT(A=,key="B",B=,1,2), error = "key specification not allowed in between names or data, only boundaries") -test(2282.14, rowwiseDT(A=,B=,1,key="B",2), error = "key specification not allowed in between names or data, only boundaries") -test(2282.15, rowwiseDT(A=,key=,B=,1,2,3,key="B"), data.table(A=1,B=3,key="B")[,key:=2][,.(A,key,B)]) -test(2282.16, rowwiseDT(key="A",key="B",A=,B=,key=,1,2,3), error = "key specified more than once") -test(2282.17, rowwiseDT(A=,B=,key="A,B",1,2), data.table(A=1,B=2, key="A,B")) +DT = data.table(A=1, B=2, key="B") +test(2282.10, rowwiseDT(key="B", A=, B=, 1, 2), DT) +test(2282.11, rowwiseDT(A=, B=, key="B", 1, 2), DT) +test(2282.12, rowwiseDT(A=,B=,1,2,key="B"), DT) +test(2282.13, rowwiseDT(A=, key="B", B=, 1, 2), error="key specification not allowed in between names or data, only boundaries") +test(2282.14, rowwiseDT(A=, B=, 1, key="B", 2), error="key specification not allowed in between names or data, only boundaries") +test(2282.15, rowwiseDT(A=, key=, B=, 1, 2, 3, key="B"), setDT(data.frame(A=1, key=2, B=3), key="B")) +test(2282.16, rowwiseDT(key="A", key="B", A=, B=, key=, 1, 2, 3), error="key specified more than once") +test(2282.17, rowwiseDT(A=, B=, key="A,B", 1, 2), data.table(A=1, B=2, key="A,B")) # as "" equals to "missing", key = "" means column name (backwards compatible) -test(2282.18, rowwiseDT(A="",B="",key="",1,2,3), data.table(A=1,B=2)[,key:=3]) +test(2282.18, rowwiseDT(A="", B="", key="", 1, 2, 3), setDT(data.frame(A=1, B=2, key=3))) # test 2283 moved tests to optimize.Rraw From d7d5b4fb3ef88817e1eb1f9e136cc2a831c3a0fc Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 23 Oct 2026 10:04:29 -0700 Subject: [PATCH 4/7] missed one --- inst/tests/tests.Rraw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index ccdec74d63..676fce35c8 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -18847,7 +18847,7 @@ test(2282.09, rowwiseDT(A=,ncols), data.table(A=ncols)) DT = data.table(A=1, B=2, key="B") test(2282.10, rowwiseDT(key="B", A=, B=, 1, 2), DT) test(2282.11, rowwiseDT(A=, B=, key="B", 1, 2), DT) -test(2282.12, rowwiseDT(A=,B=,1,2,key="B"), DT) +test(2282.12, rowwiseDT(A=, B=, 1, 2, key="B"), DT) test(2282.13, rowwiseDT(A=, key="B", B=, 1, 2), error="key specification not allowed in between names or data, only boundaries") test(2282.14, rowwiseDT(A=, B=, 1, key="B", 2), error="key specification not allowed in between names or data, only boundaries") test(2282.15, rowwiseDT(A=, key=, B=, 1, 2, 3, key="B"), setDT(data.frame(A=1, key=2, B=3), key="B")) From d34e64354ead197a8c469c28e5e97dcd7522b2ef Mon Sep 17 00:00:00 2001 From: rikivillalba Date: Mon, 26 Oct 2026 13:56:49 -0300 Subject: [PATCH 5/7] do not allow key between colnames and data --- R/rowwiseDT.R | 6 +++--- inst/tests/tests.Rraw | 13 +++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/R/rowwiseDT.R b/R/rowwiseDT.R index 43d6dcf59f..f79308bff3 100644 --- a/R/rowwiseDT.R +++ b/R/rowwiseDT.R @@ -4,7 +4,7 @@ rowwiseDT = function(...) { stopf("Must provide at least one column (use `name=`). See ?rowwiseDT for details") if (length(key_pos <- which(nms == "key" & nzchar(x))) > 0L) { if (length(key_pos) > 1L) - stopf("key specified more than once") + stopf("Key specified more than once") if (length(key <- eval(x[[key_pos]], parent.frame())) == 1L) key = cols_from_csv(key) x = x[-key_pos] @@ -17,8 +17,8 @@ rowwiseDT = function(...) { stopf("Named arguments must be empty") if (!identical(header_pos, seq_along(header_pos))) stopf("Header must be the first N arguments") - if (key_pos && !(key_pos - 1L) %in% c(0L, length(header_pos), length(x))) - stopf("key specification not allowed in between names or data, only boundaries") + if (key_pos && !key_pos %in% c(1L, length(x) + 1L)) + stopf("Key specification must be either the first or the last argument") header = nms[header_pos] ncols = length(header) body = lapply(x[-header_pos], eval, envir = parent.frame()) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 676fce35c8..dc49aaf1ac 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -18846,15 +18846,12 @@ test(2282.09, rowwiseDT(A=,ncols), data.table(A=ncols)) # tests involving key specification in rowwiseDT DT = data.table(A=1, B=2, key="B") test(2282.10, rowwiseDT(key="B", A=, B=, 1, 2), DT) -test(2282.11, rowwiseDT(A=, B=, key="B", 1, 2), DT) -test(2282.12, rowwiseDT(A=, B=, 1, 2, key="B"), DT) -test(2282.13, rowwiseDT(A=, key="B", B=, 1, 2), error="key specification not allowed in between names or data, only boundaries") -test(2282.14, rowwiseDT(A=, B=, 1, key="B", 2), error="key specification not allowed in between names or data, only boundaries") -test(2282.15, rowwiseDT(A=, key=, B=, 1, 2, 3, key="B"), setDT(data.frame(A=1, key=2, B=3), key="B")) -test(2282.16, rowwiseDT(key="A", key="B", A=, B=, key=, 1, 2, 3), error="key specified more than once") -test(2282.17, rowwiseDT(A=, B=, key="A,B", 1, 2), data.table(A=1, B=2, key="A,B")) +test(2282.11, rowwiseDT(A=, B=, 1, 2, key="B"), DT) +test(2282.12, rowwiseDT(A=, key=, B=, 1, 2, 3, key="B"), setDT(data.frame(A=1, key=2, B=3), key="B")) +test(2282.13, rowwiseDT(A=, key="B", B=, 1, 2), error="Key specification must be either the first or the last argument") +test(2282.14, rowwiseDT(key="A", key="B", A=, B=, key=, 1, 2, 3), error="key specified more than once") # as "" equals to "missing", key = "" means column name (backwards compatible) -test(2282.18, rowwiseDT(A="", B="", key="", 1, 2, 3), setDT(data.frame(A=1, B=2, key=3))) +test(2282.15, rowwiseDT(A="", B="", key="", 1, 2, 3), setDT(data.frame(A=1, B=2, key=3))) # test 2283 moved tests to optimize.Rraw From f7556627f6be9cad1c04bad5d0e1c4ff112e9cbe Mon Sep 17 00:00:00 2001 From: rikivillalba Date: Mon, 26 Oct 2026 19:11:19 -0300 Subject: [PATCH 6/7] typo --- inst/tests/tests.Rraw | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index dc49aaf1ac..1c7180b0ab 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -18849,7 +18849,7 @@ test(2282.10, rowwiseDT(key="B", A=, B=, 1, 2), DT) test(2282.11, rowwiseDT(A=, B=, 1, 2, key="B"), DT) test(2282.12, rowwiseDT(A=, key=, B=, 1, 2, 3, key="B"), setDT(data.frame(A=1, key=2, B=3), key="B")) test(2282.13, rowwiseDT(A=, key="B", B=, 1, 2), error="Key specification must be either the first or the last argument") -test(2282.14, rowwiseDT(key="A", key="B", A=, B=, key=, 1, 2, 3), error="key specified more than once") +test(2282.14, rowwiseDT(key="A", key="B", A=, B=, key=, 1, 2, 3), error="Key specified more than once") # as "" equals to "missing", key = "" means column name (backwards compatible) test(2282.15, rowwiseDT(A="", B="", key="", 1, 2, 3), setDT(data.frame(A=1, B=2, key=3))) From d8864d997336ddfe1ebad3bd794eafff952532c4 Mon Sep 17 00:00:00 2001 From: rikivillalba Date: Mon, 26 Oct 2026 19:13:33 -0300 Subject: [PATCH 7/7] update rowwiseDT manpage --- man/rowwiseDT.Rd | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/man/rowwiseDT.Rd b/man/rowwiseDT.Rd index c21f425e3d..3a4f4ef43c 100644 --- a/man/rowwiseDT.Rd +++ b/man/rowwiseDT.Rd @@ -8,7 +8,7 @@ rowwiseDT(...) } \arguments{ - \item{...}{ Arguments that define the structure of a \code{data.table}. The column names come from named arguments (like \code{col=}), which must precede the data. See Examples. } + \item{...}{ Arguments that define the structure of a \code{data.table}. The column names come from named arguments (like \code{col=}), which must precede the data. A \code{key} can also be specified, given it is the first or last argument. See Examples. } } \value{ A \code{data.table}. The default is for each column to return as a vector. However, if any entry has a length that is not one (e.g., \code{list(1, 2)}), the whole column will be converted to a list column. @@ -20,6 +20,7 @@ A \code{data.table}. The default is for each column to return as a vector. Howev rowwiseDT( A=,B=, C=, 1, "a",2:3, - 2, "b",list(5) + 2, "b",list(5), + key = "A" ) }