From 3e99eb4b8b13e915ca0a1295634ee81d47e71d23 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Thu, 10 Jul 2025 22:52:00 +0000 Subject: [PATCH 01/10] implement option, add regresion tests --- NEWS.md | 4 ++++ R/as.data.table.R | 2 +- R/onLoad.R | 11 +++++++---- inst/tests/tests.Rraw | 4 ++++ man/data.table-options.Rd | 9 +++++++++ 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/NEWS.md b/NEWS.md index eb2fbd047b..87b7838562 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,10 @@ ## data.table [v1.17.99](https://github.com/Rdatatable/data.table/milestone/35) (in development) +### POTENTIALLY BREAKING CHANGES + +1. `data.table(x=1, )`, where `` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, )` where `` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 14 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`. + ### NEW FEATURES 1. New `sort_by()` method for data.tables, [#6662](https://github.com/Rdatatable/data.table/issues/6662). It uses `forder()` to improve upon the data.frame method and also match `DT[order(...)]` behavior with respect to locale. Thanks @rikivillalba for the suggestion and PR. diff --git a/R/as.data.table.R b/R/as.data.table.R index bd7f97fa79..65c136fc92 100644 --- a/R/as.data.table.R +++ b/R/as.data.table.R @@ -162,7 +162,7 @@ as.data.table.list = function(x, xi = x[[i]] = as.POSIXct(xi) } else if (is.matrix(xi) || is.data.frame(xi)) { if (!is.data.table(xi)) { - if (is.matrix(xi) && NCOL(xi)<=1L && is.null(colnames(xi))) { # 1 column matrix naming #4124 + if (is.matrix(xi) && NCOL(xi)<=1L && is.null(colnames(xi)) && isFALSE(getOption('datatable.old.matrix.autoname'))) { # 1 column matrix naming #4124 xi = x[[i]] = c(xi) } else { xi = x[[i]] = as.data.table(xi, keep.rownames=keep.rownames) # we will never allow a matrix to be a column; always unpack the columns diff --git a/R/onLoad.R b/R/onLoad.R index 5e74bc7d23..237c86808c 100644 --- a/R/onLoad.R +++ b/R/onLoad.R @@ -73,7 +73,8 @@ # In fread and fwrite we have moved back to using getOption's default argument since it is unlikely fread and fread will be called in a loop many times, plus they # are relatively heavy functions where the overhead in getOption() would not be noticed. It's only really [.data.table where getOption default bit. # Improvement to base::getOption() now submitted (100x; 5s down to 0.05s): https://bugs.r-project.org/bugzilla/show_bug.cgi?id=17394 - opts = c("datatable.verbose"="FALSE", # datatable. + opts = c( + "datatable.verbose"="FALSE", # datatable. "datatable.optimize"="Inf", # datatable. "datatable.print.nrows"="100L", # datatable. "datatable.print.topn"="5L", # datatable. @@ -85,12 +86,14 @@ "datatable.show.indices"="FALSE", # for print.data.table "datatable.allow.cartesian"="FALSE", # datatable. "datatable.join.many"="TRUE", # mergelist, [.data.table #4383 #914 - "datatable.dfdispatchwarn"="TRUE", # not a function argument - "datatable.warnredundantby"="TRUE", # not a function argument + "datatable.dfdispatchwarn"="TRUE", # not a function argument + "datatable.warnredundantby"="TRUE", # not a function argument "datatable.alloccol"="1024L", # argument 'n' of alloc.col. Over-allocate 1024 spare column slots "datatable.auto.index"="TRUE", # DT[col=="val"] to auto add index so 2nd time faster "datatable.use.index"="TRUE", # global switch to address #1422 - "datatable.prettyprint.char" = NULL # FR #1091 + "datatable.prettyprint.char" = NULL, # FR #1091 + "datatable.old.matrix.autoname"="TRUE", # #7145: how data.table(x=1, matrix(1)) is auto-named set to change + NULL ) for (i in setdiff(names(opts),names(options()))) { eval(parse(text=paste0("options(",i,"=",opts[i],")"))) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index aceeb77f89..19059c86dd 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21317,6 +21317,10 @@ colnames(M) = c('A', '') test(2321.26, as.data.table(M), data.table(A=1:3, V2=4:6)) test(2321.27, as.data.table(M, keep.rownames='id'), data.table(id=c('a', 'b', 'c'), A=1:3, V2=4:6)) +# also respect old auto-naming rules by default (to be deprecated) +test(2321.28, names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V1", "c", "V4")) +test(2321.29, options=c(datatable.old.matrix.autoname=FALSE), names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V2", "c", "V4")) + # New fctr() helper: like factor() but retaining order by default #4837 test(2322.01, levels(fctr(c("b","a","c"))), c("b","a","c")) test(2322.02, levels(fctr(c(3,1,2))), c("3","1","2")) diff --git a/man/data.table-options.Rd b/man/data.table-options.Rd index 775657d899..63c79fdf6a 100644 --- a/man/data.table-options.Rd +++ b/man/data.table-options.Rd @@ -108,6 +108,15 @@ } } +\section{Back-compatibility Options}{ + \describe{ + \item{\code{datatable.old.matrix.autoname}}{Logical, default \code{TRUE}. Governs how the output of + expressions like \code{data.table(x=1, cbind(1))} will be named. When \code{TRUE}, it will be named + \code{V1}, otherwise it will be named \code{V2}. + } + } +} + \seealso{ \code{\link[base]{options}}, \code{\link[base]{getOption}}, From eb9bb87bef9b61a0793db2f57a22d4e6c65267c6 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 11 Jul 2025 07:22:10 +0000 Subject: [PATCH 02/10] regression test for dup-named case --- inst/tests/tests.Rraw | 3 +++ 1 file changed, 3 insertions(+) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 19059c86dd..5909d59a76 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21320,6 +21320,9 @@ test(2321.27, as.data.table(M, keep.rownames='id'), data.table(id=c('a', 'b', 'c # also respect old auto-naming rules by default (to be deprecated) test(2321.28, names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V1", "c", "V4")) test(2321.29, options=c(datatable.old.matrix.autoname=FALSE), names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V2", "c", "V4")) +# particularly buggy old behavior: can easily result in duplicate names +test(2321.30, names(data.table(cbind(1), cbind(2))), c("V1", "V1")) +test(2321.31, options=c(datatable.old.matrix.autoname=FALSE), names(data.table(cbind(1), cbind(2))), c("V1", "V2")) # New fctr() helper: like factor() but retaining order by default #4837 test(2322.01, levels(fctr(c("b","a","c"))), c("b","a","c")) From e15e35e7e7adf21487c4fe8b67550028b17e3f98 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 11 Jul 2025 07:50:14 +0000 Subject: [PATCH 03/10] fix for updated behavior --- R/as.data.table.R | 4 ++-- inst/tests/tests.Rraw | 29 ++++++++++++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/R/as.data.table.R b/R/as.data.table.R index 65c136fc92..9743b36114 100644 --- a/R/as.data.table.R +++ b/R/as.data.table.R @@ -50,7 +50,7 @@ as.data.table.matrix = function(x, keep.rownames=FALSE, key=NULL, ...) { ans = data.table(rn=rownames(x), x, keep.rownames=FALSE) # auto-inferred name 'x' is not back-compatible & inconsistent, #7145 if (ncol(x) == 1L && is.null(colnames(x))) - setnames(ans, 'x', 'V1') + setnames(ans, 'x', 'V1', skip_absent=TRUE) if (is.character(keep.rownames)) setnames(ans, 'rn', keep.rownames[1L]) return(ans) @@ -162,7 +162,7 @@ as.data.table.list = function(x, xi = x[[i]] = as.POSIXct(xi) } else if (is.matrix(xi) || is.data.frame(xi)) { if (!is.data.table(xi)) { - if (is.matrix(xi) && NCOL(xi)<=1L && is.null(colnames(xi)) && isFALSE(getOption('datatable.old.matrix.autoname'))) { # 1 column matrix naming #4124 + if (is.matrix(xi) && NCOL(xi)==1L && is.null(colnames(xi)) && isFALSE(getOption('datatable.old.matrix.autoname'))) { # 1 column matrix naming #4124 xi = x[[i]] = c(xi) } else { xi = x[[i]] = as.data.table(xi, keep.rownames=keep.rownames) # we will never allow a matrix to be a column; always unpack the columns diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 5909d59a76..2752d33b10 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21319,10 +21319,23 @@ test(2321.27, as.data.table(M, keep.rownames='id'), data.table(id=c('a', 'b', 'c # also respect old auto-naming rules by default (to be deprecated) test(2321.28, names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V1", "c", "V4")) -test(2321.29, options=c(datatable.old.matrix.autoname=FALSE), names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V2", "c", "V4")) -# particularly buggy old behavior: can easily result in duplicate names -test(2321.30, names(data.table(cbind(1), cbind(2))), c("V1", "V1")) -test(2321.31, options=c(datatable.old.matrix.autoname=FALSE), names(data.table(cbind(1), cbind(2))), c("V1", "V2")) +test(2321.29, names(data.table(cbind(1), cbind(2))), c("V1", "V1")) +# also test behavior with a 0-column matrix +M = cbind(1:3) +test(2321.30, data.table(M[, 0L]), data.table(NULL)) +test(2321.31, data.table(a=1:3, M[, 0L]), data.table(a=1:3)) + +local({ + old = options(datatable.old.matrix.autoname=FALSE) + on.exit(options(old)) + + test(2321.32, names(data.table(a=1, cbind(2), c=3, 4)), c("a", "V2", "c", "V4")) + # particularly buggy old behavior: can easily result in duplicate names + test(2321.33, names(data.table(cbind(1), cbind(2))), c("V1", "V2")) + M = cbind(1:3) + test(2321.34, data.table(M[, 0L]), data.table(NULL)) + test(2321.35, data.table(a=1:3, M[, 0L]), data.table(a=1:3)) +}) # New fctr() helper: like factor() but retaining order by default #4837 test(2322.01, levels(fctr(c("b","a","c"))), c("b","a","c")) @@ -21429,7 +21442,9 @@ DF <- data.frame(row.names = letters[1:6], V = 1:6) # Test data.frame with e test(2330.6, as.data.table(list(a = 6:1, DF), keep.rownames=TRUE), data.table(rn=letters[1:6], a=6:1, V=1:6)) z <- setNames(1:3, rep("", 3)) # vector with all-empty names # behaviour with all-empty row names -test(2330.7, as.data.table(list(z), keep.rownames=TRUE), data.table(rn=rep("", 3), V1=1:3)) +test(2330.7, as.data.table(list(z), keep.rownames=TRUE), data.table(rn="", V1=1:3)) -M <- matrix(1:6, nrow=3, dimnames=list(rep("", 3), c("V1", "V2"))) # test of list(M) for empty-rowname'd matrix input -test(2330.8, as.data.table(list(M), keep.rownames=TRUE), data.table(rn=rep("", 3), V1=1:3, V2=4:6)) +M <- matrix(1:6, nrow=3, dimnames=list(rep("", 3L), c("V1", "V2"))) # test of list(M) for empty-rowname'd matrix input +test(2330.8, as.data.table(list(M), keep.rownames=TRUE), data.table(rn="", V1=1:3, V2=4:6)) +# 0-column input can still provide rownames +test(2330.9, as.data.table(list(M[, 0L], 1:3), keep.rownames=TRUE), data.table(rn="", V2=1:3)) From 10f42dc817de2c0c2afb5c7068b07f517816cf8e Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 11 Jul 2025 01:07:37 -0700 Subject: [PATCH 04/10] need option set for original tests --- inst/tests/tests.Rraw | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 2752d33b10..085296960e 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21276,10 +21276,15 @@ if (test_R.utils) local({ }) # Create a data.table when one vector is transposed doesn't respect the name defined by user #4124 -test(2321.01, DT <- data.table(a=1:2, b=matrix(1:2)), data.table(a=1:2, b=1:2)) -test(2321.02, names(DT), names(data.frame(a=1:2, b=matrix(1:2)))) -test(2321.03, DT <- data.table(a=integer(), b=matrix(1L, nrow=0L, ncol=1L)), data.table(a=integer(), b=integer())) -test(2321.04, names(DT), names(data.frame(a=integer(), b=matrix(1L, nrow=0L, ncol=1L)))) +local({ + old = options(datatable.old.matrix.autoname=FALSE) + on.exit(options(old)) + + test(2321.01, DT <- data.table(a=1:2, b=matrix(1:2)), data.table(a=1:2, b=1:2)) + test(2321.02, names(DT), names(data.frame(a=1:2, b=matrix(1:2)))) + test(2321.03, DT <- data.table(a=integer(), b=matrix(1L, nrow=0L, ncol=1L)), data.table(a=integer(), b=integer())) + test(2321.04, names(DT), names(data.frame(a=integer(), b=matrix(1L, nrow=0L, ncol=1L)))) +}) ## but respect named column vectors test(2321.05, DT <- data.table(a=1:2, cbind(b=3:4)), data.table(a=1:2, b=3:4)) test(2321.06, names(DT), names(data.frame(a=1:2, cbind(b=3:4)))) From ecfd14f10feb3c7461158859b23e89c3f937ce13 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 11 Jul 2025 09:27:41 -0700 Subject: [PATCH 05/10] refine wording --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 87b7838562..3d7332b60f 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,7 @@ ## data.table [v1.17.99](https://github.com/Rdatatable/data.table/milestone/35) (in development) -### POTENTIALLY BREAKING CHANGES +### NOTICE OF INTENDED FUTURE POTENTIAL BREAKING CHANGES 1. `data.table(x=1, )`, where `` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, )` where `` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 14 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`. From 6905c57a50b89119c86a065f73a7a91b4751ac05 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Fri, 11 Jul 2025 12:03:25 -0700 Subject: [PATCH 06/10] item number fix --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 6eb127074e..f4eb9bcbc5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,7 @@ ### NOTICE OF INTENDED FUTURE POTENTIAL BREAKING CHANGES -1. `data.table(x=1, )`, where `` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, )` where `` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 14 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`. +1. `data.table(x=1, )`, where `` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, )` where `` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 10 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`. ### NEW FEATURES From cb109742528756f69e66fd9dab4631e9c9ad0087 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Mon, 14 Jul 2025 21:53:00 +0000 Subject: [PATCH 07/10] Regression test for having fixed #5367 --- inst/tests/tests.Rraw | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 085296960e..bd45a0d5d9 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21340,6 +21340,10 @@ local({ M = cbind(1:3) test(2321.34, data.table(M[, 0L]), data.table(NULL)) test(2321.35, data.table(a=1:3, M[, 0L]), data.table(a=1:3)) + + # a more subtle version of this as expressed in #5367 + DT <- data.table(Counts=c(10, 20), Severity=c(1, 2)) + test(2321.36, names(DT[,.(New_name = Severity %*% Counts)]), "New_name") }) # New fctr() helper: like factor() but retaining order by default #4837 From 7149ba03ab4e8f877deb7421333346ab295d34e3 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Mon, 14 Jul 2025 21:54:37 +0000 Subject: [PATCH 08/10] correct NEWS reference --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index f4eb9bcbc5..26bc7a6695 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,7 @@ ### NOTICE OF INTENDED FUTURE POTENTIAL BREAKING CHANGES -1. `data.table(x=1, )`, where `` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, )` where `` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 10 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`. +1. `data.table(x=1, )`, where `` is an expression resulting in a 1-column matrix without column names, will eventually have names `x` and `V2`, not `x` and `V1`, consistent with `data.table(x=1, )` where `` results in an atomic vector, for example `data.table(x=1, cbind(1))` and `data.table(x=1, 1)` will both have columns named `x` and `V2`. In this release, the matrix case continues to be named `V1`, but the new behavior can be activated by setting `options(datatable.old.matrix.autoname)` to `FALSE`. See point 5 under Bug Fixes for more context; this change will provide more internal consistency as well as more consistency with `data.frame()`. ### NEW FEATURES From d2887d3b414fadf10e30fd69e39a05275eebd2cc Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Mon, 14 Jul 2025 21:55:53 +0000 Subject: [PATCH 09/10] include other fixed bugs in NEWS --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 26bc7a6695..e62d2f2a9e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -60,7 +60,7 @@ 4. In rare cases, `data.table` failed to expand ALTREP columns when assigning a full column by reference. This could result in the target column getting modified unintentionally if the next call to the data.table was a modification by reference of the source column. E.g. in `DT[, b := as.character(a)]` the string conversion gets deferred and subsequent modification of column `a` would also modify column `b`, [#5400](https://github.com/Rdatatable/data.table/issues/5400). Thanks to @aquasync for the report and Václav Tlapák for the PR. -5. `data.table()` function is now more aligned with `data.frame()` with respect to the names of the output when one of its inputs is a single-column matrix object, [#4124](https://github.com/Rdatatable/data.table/issues/4124). Thanks @PavoDive for the report, @jangorecki for the PR, and @MichaelChirico for a follow-up for back-compatibility. +5. `data.table()` function is now more aligned with `data.frame()` with respect to the names of the output when one of its inputs is a single-column matrix object, [#4124](https://github.com/Rdatatable/data.table/issues/4124), [#3193](https://github.com/Rdatatable/data.table/issues/3193), and [#5367](https://github.com/Rdatatable/data.table/issues/5367). Thanks @PavoDive for the report, @jangorecki for the PR, and @MichaelChirico for a follow-up for back-compatibility. 6. Including an `ITime` object as a named input to `data.frame()` respects the provided name, i.e. `data.frame(a = as.ITime(...))` will have column `a`, [#4673](https://github.com/Rdatatable/data.table/issues/4673). Thanks @shrektan for the report and @MichaelChirico for the fix. From 33f5ec0a5f02d756c3f71981ff707b6b23dcaf03 Mon Sep 17 00:00:00 2001 From: Michael Chirico Date: Mon, 14 Jul 2025 21:57:22 +0000 Subject: [PATCH 10/10] fix test numbering --- inst/tests/tests.Rraw | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 86abfb7dff..cd4234e572 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21455,9 +21455,9 @@ z <- setNames(1:3, rep("", 3)) # vector with all-empty names # behaviour wi test(2330.7, as.data.table(list(z), keep.rownames=TRUE), data.table(rn="", V1=1:3)) M <- matrix(1:6, nrow=3, dimnames=list(rep("", 3L), c("V1", "V2"))) # test of list(M) for empty-rowname'd matrix input -test(2332.8, as.data.table(list(M), keep.rownames=TRUE), data.table(rn="", V1=1:3, V2=4:6)) +test(2330.8, as.data.table(list(M), keep.rownames=TRUE), data.table(rn="", V1=1:3, V2=4:6)) # 0-column input can still provide rownames -test(2332.9, as.data.table(list(M[, 0L], 1:3), keep.rownames=TRUE), data.table(rn="", V2=1:3)) +test(2330.9, as.data.table(list(M[, 0L], 1:3), keep.rownames=TRUE), data.table(rn="", V2=1:3)) # .SD reference in '...' passed to lapply(FUN=) is recognized as data.table test(2331, lapply(list(data.table(a=1:2)), `[`, j=.SD[1L]), list(data.table(a=1L)))