diff --git a/NEWS.md b/NEWS.md index 4a62f35888..087bd15078 100644 --- a/NEWS.md +++ b/NEWS.md @@ -42,6 +42,8 @@ 10. `subset()` method for data.tables supports `drop = TRUE` for consistency to data.frame, [#7859](https://github.com/Rdatatable/data.table/issues/7859). Thanks @MichaelChirico for the report and fix. +11. `setnafill()` now accepts a logical vector for the `cols` argument, which must be the same length as the number of columns in `x`, [#4113](https://github.com/Rdatatable/data.table/issues/4113). Thanks to @MichaelChirico for the suggestion and @venom1204 for the PR. + ### BUG FIXES 1. `fread()` with `skip=0` and `(header=TRUE|FALSE)` no longer skips the first row when it has fewer fields than subsequent rows, [#7463](https://github.com/Rdatatable/data.table/issues/7463). Thanks @emayerhofer for the report and @ben-schwen for the fix. diff --git a/R/shift.R b/R/shift.R index 1c68d13c41..a1ece5f8e2 100644 --- a/R/shift.R +++ b/R/shift.R @@ -33,5 +33,10 @@ nafill = function(x, type=c("const","locf","nocb"), fill=NA, nan=NA) { setnafill = function(x, type=c("const","locf","nocb"), fill=NA, nan=NA, cols=seq_along(x)) { type = match.arg(type) + if (is.logical(cols)) { + if (length(cols) != length(x)) stopf("'cols' is a logical vector of length %d but there are %d columns", length(cols), length(x)) + if (anyNA(cols)) stopf("'cols' contains NA at position %d", which(is.na(cols))[1L]) + cols = which(cols) + } invisible(.Call(CnafillR, x, type, fill, nan_is_na(nan), TRUE, cols)) } diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index ffadaa6c3f..372ee9837a 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -21939,3 +21939,14 @@ test(2384.3, print(DT, na.print=".", topn=2, col.names="none", row.names=FALSE, output=c(" .\n e\n ---\n w\n .")) DT = data.table(a=c("x\ny","z"), b=1:2) test(2384.4, print(DT, col.names="none", row.names=FALSE, class=FALSE), output=c(" x\\ny 1\n z 2")) + +# #4113 setnafill could accept cols=logical(ncol(x)) +DT = data.table(a=c(1,NA,3), b=c(4,NA,6), c=c(7,NA,9)) +test(2385.01, setnafill(copy(DT), type="locf", cols=c(TRUE,FALSE,TRUE)), setnafill(copy(DT), type="locf", cols=c(1L,3L))) +test(2385.02, setnafill(copy(DT), type="locf", cols=c(TRUE,FALSE,TRUE)), setnafill(copy(DT), type="locf", cols=c("a","c"))) +DT2 = data.table(a=c(1,NA), b=c(2,NA)) +test(2385.03, {before=copy(DT2); setnafill(DT2, type="locf", cols=c(FALSE,FALSE)); identical(DT2, before)}, TRUE) +DT3 = data.table(a=c(1,NA), b=c("x",NA), c=c(3,NA)) +test(2385.04, setnafill(copy(DT3), type="locf", cols=sapply(DT3, is.numeric)), data.table(a=c(1,1), b=c("x",NA), c=c(3,3))) +test(2385.05, setnafill(data.table(a=1,b=2,c=3), type="locf", cols=c(TRUE,NA,FALSE)), error="'cols' contains NA at position 2") +test(2385.06, setnafill(data.table(a=1,b=2,c=3), type="locf", cols=c(TRUE,FALSE)), error="'cols' is a logical vector of length 2 but there are 3 columns") diff --git a/man/nafill.Rd b/man/nafill.Rd index 90c4b1c5c6..af04ff4f02 100644 --- a/man/nafill.Rd +++ b/man/nafill.Rd @@ -18,7 +18,7 @@ setnafill(x, type=c("const", "locf", "nocb"), fill=NA, nan=NA, cols=seq_along(x) \item{type}{ Character, one of \emph{"const"}, \emph{"locf"} or \emph{"nocb"}. Defaults to \code{"const"}. } \item{fill}{ Value to be used to replace missing observations. See examples. } \item{nan}{ Either \code{NaN} or \code{NA}; if the former, \code{NaN} is treated as distinct from \code{NA}, otherwise, they are treated the same during replacement. See Examples. } - \item{cols}{ Numeric or character vector specifying columns to be updated. } + \item{cols}{ Numeric, character or logical vector specifying columns to be updated. A logical vector must be the same length as the number of columns in \code{x}. } } \details{ Supported types are \emph{logical}, \emph{integer}, \emph{double}, \emph{character}, and \emph{factor}, as well as classes built on top of these such as \code{Date}, \code{IDate}, and \code{POSIXct}.