diff --git a/NEWS.md b/NEWS.md index bc5147107..d96c157fc 100644 --- a/NEWS.md +++ b/NEWS.md @@ -36,6 +36,8 @@ 10. `measure` now supports user-specified `cols` argument, which can be useful to specify a subset of columns to `melt`, without having to use a regex, [#5063](https://github.com/Rdatatable/data.table/issues/5063). Thanks to @UweBlock and @Henrik-P for reporting, and @tdhock for the PR. +11. `setcolorder()` gains `skip_absent` to ignore unrecognized columns (i.e. columns included in `neworder` but not present in the data), [#6044, #6068](https://github.com/Rdatatable/data.table/pull/6044). Default behavior (`skip_absent=FALSE`) remains unchanged, i.e. unrecognized columns result in an error. Thanks to @sluga for the suggestion and @sluga & @Nj221102 for the PRs. + ## BUG FIXES 1. `unique()` returns a copy the case when `nrows(x) <= 1` instead of a mutable alias, [#5932](https://github.com/Rdatatable/data.table/pull/5932). This is consistent with existing `unique()` behavior when the input has no duplicates but more than one row. Thanks to @brookslogan for the report and @dshemetov for the fix. diff --git a/R/data.table.R b/R/data.table.R index 89309e58b..396cd8886 100644 --- a/R/data.table.R +++ b/R/data.table.R @@ -2684,7 +2684,7 @@ setnames = function(x,old,new,skip_absent=FALSE) { invisible(x) } -setcolorder = function(x, neworder=key(x), before=NULL, after=NULL) # before/after #4358 +setcolorder = function(x, neworder=key(x), before=NULL, after=NULL,skip_absent=FALSE) # before/after #4358 { if (is.character(neworder) && anyDuplicated(names(x))) stopf("x has some duplicated column name(s): %s. Please remove or rename the duplicate(s) and try again.", brackify(unique(names(x)[duplicated(names(x))]))) @@ -2692,7 +2692,8 @@ setcolorder = function(x, neworder=key(x), before=NULL, after=NULL) # before/af stopf("Provide either before= or after= but not both") if (length(before)>1L || length(after)>1L) stopf("before=/after= accept a single column name or number, not more than one") - neworder = colnamesInt(x, neworder, check_dups=FALSE) # dups are now checked inside Csetcolorder below + neworder = colnamesInt(x, neworder, check_dups=FALSE, skip_absent=skip_absent) # dups are now checked inside Csetcolorder below + neworder = neworder[neworder != 0] # tests 498.11, 498.13 fail w/o this if (length(before)) neworder = c(setdiff(seq_len(colnamesInt(x, before) - 1L), neworder), neworder) if (length(after)) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 28532eb59..5a6b4df14 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -1585,6 +1585,18 @@ test(498.03, setcolorder(DT, 1, after=3), data.table(b=2, c=3, a=1)) test(498.04, setcolorder(DT, 3, before=1), data.table(a=1, b=2, c=3)) test(498.05, setcolorder(DT, 1, before=1, after=1), error="Provide either before= or after= but not both") test(498.06, setcolorder(DT, 1, before=1:2), error="before=/after= accept a single column name or number, not more than one") +# skip_absent, #6044 +test(498.07, setcolorder(DT, skip_absent = 'TRUE'), error = 'TRUE or FALSE') +test(498.08, setcolorder(DT, skip_absent = 1), error = 'TRUE or FALSE') +test(498.09, setcolorder(DT, skip_absent = c(TRUE, FALSE)), error = 'TRUE or FALSE') +test(498.10, setcolorder(DT, c('d', 'c', 'b', 'a')), error = 'non-existing column') +test(498.11, setcolorder(DT, c('d', 'c', 'b', 'a'), skip_absent = TRUE), data.table(c=3, b=2, a=1)) +test(498.12, setcolorder(DT, 4:1), error = 'non-existing column') +test(498.13, setcolorder(DT, 4:1, skip_absent = TRUE), data.table(a=1, b=2, c=3)) +test(498.14, setcolorder(DT, c(1, 1, 2, 3), skip_absent = TRUE), error = '!=') +## `c` is not dropped +test(498.15, setcolorder(DT, neworder = 'b', skip_absent = TRUE), data.table(b=2, a=1, c=3)) +test(498.16, setcolorder(DT, neworder = c('a', 'b', 'd'), skip_absent = TRUE), data.table(a=1, b=2, c=3)) # test first group listens to nomatch when j uses join inherited scope. x <- data.table(x=c(1,3,8),x1=10:12, key="x") diff --git a/man/setcolorder.Rd b/man/setcolorder.Rd index e11a24d79..5d63ffc93 100644 --- a/man/setcolorder.Rd +++ b/man/setcolorder.Rd @@ -9,12 +9,13 @@ } \usage{ -setcolorder(x, neworder=key(x), before=NULL, after=NULL) +setcolorder(x, neworder=key(x), before=NULL, after=NULL,skip_absent=FALSE) } \arguments{ \item{x}{ A \code{data.table}. } \item{neworder}{ Character vector of the new column name ordering. May also be column numbers. If \code{length(neworder) < length(x)}, the specified columns are moved in order to the "front" of \code{x}. By default, \code{setcolorder} without a specified \code{neworder} moves the key columns in order to the "front" of \code{x}. } \item{before, after}{ If one of them (not both) was provided with a column name or number, \code{neworder} will be inserted before or after that column. } + \item{skip_absent}{ Logical, default \code{FALSE}. If \code{neworder} includes columns not present in \code{x}, \code{TRUE} will silently ignore them, whereas \code{FALSE} will throw an error. } \details{ To reorder \code{data.table} columns, the idiomatic way is to use \code{setcolorder(x, neworder)}, instead of doing \code{x <- x[, ..neworder]} (or \code{x <- x[, neworder, with=FALSE]}). This is because the latter makes an entire copy of the \code{data.table}, which maybe unnecessary in most situations. \code{setcolorder} also allows column numbers instead of names for \code{neworder} argument, although we recommend using names as a good programming practice.