Skip to content

Commit 66272b2

Browse files
committed
updated logic
1 parent 813ed95 commit 66272b2

6 files changed

Lines changed: 61 additions & 26 deletions

File tree

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848

4949
13. `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.
5050

51+
14. `transpose()` and `tstrsplit()` gain a `keep` argument to specify which columns to return and in what order, [#5250](https://github.com/Rdatatable/data.table/issues/5250). When `keep` is used, the operation is now significantly more memory-efficient because memory for "throwaway" columns is never allocated in the C engine. Thanks to @MichaelChirico for the suggestion and @venom1204 the implementation.
52+
5153
### BUG FIXES
5254

5355
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.

R/transpose.R

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
transpose = function(l, fill=NA, ignore.empty=FALSE, keep.names=NULL, make.names=NULL, list.cols=FALSE) {
1+
transpose = function(l, fill=NA, ignore.empty=FALSE, keep.names=NULL, make.names=NULL, list.cols=FALSE, keep=NULL) {
2+
if (!is.null(keep)) keep = as.integer(keep)
23
if (!is.null(make.names)) {
34
stopifnot(length(make.names)==1L)
45
if (is.character(make.names)) {
@@ -14,7 +15,7 @@ transpose = function(l, fill=NA, ignore.empty=FALSE, keep.names=NULL, make.names
1415
colnames = as.character(l[[make.names]])
1516
l = if (is.data.table(l)) l[,-make.names,with=FALSE] else l[-make.names]
1617
}
17-
ans = .Call(Ctranspose, l, fill, ignore.empty, keep.names, list.cols)
18+
ans = .Call(Ctranspose, l, fill, ignore.empty, keep.names, list.cols, keep)
1819
if (!is.null(make.names)) setattr(ans, "names", c(keep.names, colnames))
1920
else if (is.data.frame(l)) # including data.table but not plain list
2021
setattr(ans, "names", c(keep.names, paste0("V", seq_len(length(ans)-length(keep.names)))))
@@ -30,18 +31,22 @@ tstrsplit = function(x, ..., fill=NA, type.convert=FALSE, keep, names=FALSE, rev
3031
stopf("'rev' must be TRUE or FALSE.")
3132
ans = strsplit(as.character(x), ...)
3233
if (rev) ans = lapply(ans, base::rev)
33-
ans = transpose(ans, fill=fill, ignore.empty=FALSE)
34-
3534
if (!missing(keep)) {
3635
keep = suppressWarnings(as.integer(keep))
37-
chk = min(keep) >= min(1L, length(ans)) & max(keep) <= length(ans)
38-
if (!isTRUE(chk) || !length(keep))
39-
stopf("'keep' should contain integer values between %d and %d.", min(1L, length(ans)), length(ans))
36+
maxlen = if (length(ans)) max(vapply(ans, length, 0L)) else 0L
37+
chk = min(keep, na.rm=TRUE) >= min(1L, maxlen) && max(keep, na.rm=TRUE) <= maxlen
38+
if (!isTRUE(chk) || !length(keep) || anyNA(keep))
39+
stopf("'keep' should contain integer values between %d and %d.", min(1L, maxlen), maxlen)
40+
can_keep_early = isFALSE(type.convert)
41+
ans = transpose(ans, fill=fill, ignore.empty=FALSE, keep = if (can_keep_early) keep else NULL)
4042
} else {
43+
ans = transpose(ans, fill=fill, ignore.empty=FALSE)
4144
keep = seq_along(ans)
4245
}
43-
if (isFALSE(type.convert))
44-
ans = ans[keep]
46+
47+
if (isFALSE(type.convert)) {
48+
if (length(ans) != length(keep)) ans = ans[keep]
49+
}
4550
# Implementing #1094, but default FALSE
4651
else if (isTRUE(type.convert))
4752
ans = lapply(ans[keep], type.convert, as.is=TRUE)

inst/tests/tests.Rraw

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21985,3 +21985,11 @@ DT3 = data.table(a=c(1,NA), b=c("x",NA), c=c(3,NA))
2198521985
test(2387.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)))
2198621986
test(2387.05, setnafill(copy(DT3), type="locf", cols=c(TRUE,NA,FALSE)), error="'cols' contains NA at position 2")
2198721987
test(2387.06, setnafill(copy(DT3), type="locf", cols=c(TRUE,FALSE)), error="'cols' is a logical vector of length 2 but there are 3 columns")
21988+
21989+
# transpose() supports keep argument, #5250
21990+
x = list(1:5, 6:10, 11:15)
21991+
test(2388.01, transpose(x, keep=1:2), transpose(x)[1:2])
21992+
test(2388.02, transpose(x, keep=c(5L,3L,1L)), transpose(x)[c(5,3,1)])
21993+
test(2388.03, transpose(x, keep=c(1L,1L,3L)), transpose(x)[c(1,1,3)])
21994+
test(2388.04, transpose(x, keep=NULL), transpose(x))
21995+
test(2388.05, transpose(x, keep=1:2, fill=NA), transpose(x, fill=NA)[1:2])

man/transpose.Rd

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
\usage{
99
transpose(l, fill=NA, ignore.empty=FALSE, keep.names=NULL,
10-
make.names=NULL, list.cols=FALSE)
10+
make.names=NULL, list.cols=FALSE, keep=NULL)
1111
}
1212
\arguments{
1313
\item{l}{ A list, data.frame or data.table. }
@@ -16,6 +16,7 @@ transpose(l, fill=NA, ignore.empty=FALSE, keep.names=NULL,
1616
\item{keep.names}{The name of the first column in the result containing the names of the input; e.g. \code{keep.names="rn"}. By default \code{NULL} and the names of the input are discarded.}
1717
\item{make.names}{The name or number of a column in the input to use as names of the output; e.g. \code{make.names="rn"}. By default \code{NULL} and default names are given to the output columns.}
1818
\item{list.cols}{Default is \code{FALSE}. \code{TRUE} will avoid promoting types and return columns of type \code{list} instead. \code{factor} will always be cast to \code{character}.}
19+
\item{keep}{An integer vector of column indices to keep and return. The columns will be returned in the order specified. If \code{NULL} (default), all columns are returned. This is much more memory efficient than transposing the entire list and then subsetting the result.}
1920
}
2021
\details{
2122
The list elements (or columns of \code{data.frame}/\code{data.table}) should be all \code{atomic}. If list elements are of unequal lengths, the value provided in \code{fill} will be used so that the resulting list always has all elements of identical lengths. The class of input object is also preserved in the transposed result.
@@ -48,8 +49,9 @@ l = list(1:3, c("a", "b", "c"))
4849
lapply(seq(length(l[[1]])), function(x) lapply(l, `[[`, x))
4950
transpose(l, list.cols=TRUE)
5051

51-
ll = list(nm=c('x', 'y'), 1:2, 3:4)
52-
transpose(ll, make.names="nm")
52+
ll = list(1:5, 6:10)
53+
transpose(ll, keep=1:2)
54+
transpose(ll, keep=c(3, 1))
5355
}
5456
\seealso{
5557
\code{\link{data.table}}, \code{\link{tstrsplit}}

src/data.table.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,7 +445,7 @@ SEXP lookup(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
445445
SEXP overlaps(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
446446
SEXP whichwrapper(SEXP, SEXP);
447447
SEXP shift(SEXP, SEXP, SEXP, SEXP);
448-
SEXP transpose(SEXP, SEXP, SEXP, SEXP, SEXP);
448+
SEXP transpose(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
449449
SEXP anyNA(SEXP, SEXP);
450450
SEXP setlevels(SEXP, SEXP, SEXP);
451451
SEXP rleid(SEXP, SEXP);

src/transpose.c

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#include <Rdefines.h>
33
#include <time.h>
44

5-
SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg, SEXP keepNamesArg, SEXP listColsArg)
5+
SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg, SEXP keepNamesArg, SEXP listColsArg, SEXP keepArg)
66
{
77
int nprotect = 0;
88
if (!isNewList(l))
@@ -22,6 +22,11 @@ SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg, SEXP keepNamesArg, SEXP listCo
2222
error(_("'%s' must be TRUE or FALSE"), "list.cols");
2323
const bool listCol = LOGICAL_RO(listColsArg)[0];
2424

25+
const bool use_keep = !isNull(keepArg);
26+
if (use_keep && !isInteger(keepArg)) error(_("'keep' must be an integer vector."));
27+
const int *keep_ptr = use_keep ? INTEGER_RO(keepArg) : NULL;
28+
const int keep_len = use_keep ? LENGTH(keepArg) : 0;
29+
2530
// preprocessing
2631
int maxlen = 0, zerolen = 0;
2732
SEXPTYPE maxtype = 0;
@@ -37,8 +42,16 @@ SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg, SEXP keepNamesArg, SEXP listCo
3742
if (type > maxtype) maxtype = type;
3843
}
3944
if (listCol) maxtype = VECSXP; // need to keep preprocessing for zerolen
45+
if (use_keep) {
46+
for (int i=0; i<keep_len; i++) {
47+
if (keep_ptr[i] == NA_INTEGER || keep_ptr[i] < 1 || keep_ptr[i] > maxlen)
48+
error(_("'keep' index %d is out of bounds [1, %d]"), keep_ptr[i], maxlen);
49+
}
50+
}
51+
const int num_cols = use_keep ? keep_len : maxlen;
52+
4053
fill = PROTECT(coerceVector(fill, maxtype)); nprotect++;
41-
SEXP ans = PROTECT(allocVector(VECSXP, maxlen + rn)); nprotect++;
54+
SEXP ans = PROTECT(allocVector(VECSXP, num_cols + rn)); nprotect++;
4255
const int anslen = (ignore) ? (ln - zerolen) : ln;
4356
if (rn) {
4457
SEXP tt;
@@ -48,7 +61,7 @@ SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg, SEXP keepNamesArg, SEXP listCo
4861
if (length(VECTOR_ELT(l, i))) SET_STRING_ELT(tt, j++, STRING_ELT(lNames, i));
4962
}
5063
}
51-
for (int i = 0; i < maxlen; i++) {
64+
for (int i = 0; i < num_cols; i++) {
5265
SET_VECTOR_ELT(ans, i + rn, allocVector(maxtype, anslen));
5366
}
5467
const SEXP *ansp = SEXPPTR_RO(ans);
@@ -63,34 +76,39 @@ SEXP transpose(SEXP l, SEXP fill, SEXP ignoreArg, SEXP keepNamesArg, SEXP listCo
6376
case LGLSXP: {
6477
const int *ili = LOGICAL_RO(li);
6578
const int ifill = LOGICAL_RO(fill)[0];
66-
for (int j = 0; j < maxlen; j++) {
67-
LOGICAL(ansp[j + rn])[k] = j < len ? ili[j] : ifill;
79+
for (int j = 0; j < num_cols; j++) {
80+
int idx = use_keep ? (keep_ptr[j] - 1) : j;
81+
LOGICAL(ansp[j + rn])[k] = idx < len ? ili[idx] : ifill;
6882
}
6983
} break;
7084
case INTSXP: {
7185
const int *ili = INTEGER_RO(li);
7286
const int ifill = INTEGER_RO(fill)[0];
73-
for (int j = 0; j < maxlen; j++) {
74-
INTEGER(ansp[j + rn])[k] = j < len ? ili[j] : ifill;
87+
for (int j = 0; j < num_cols; j++) {
88+
int idx = use_keep ? (keep_ptr[j] - 1) : j;
89+
INTEGER(ansp[j + rn])[k] = idx < len ? ili[idx] : ifill;
7590
}
7691
} break;
7792
case REALSXP: {
7893
const double *dli = REAL_RO(li);
7994
const double dfill = REAL_RO(fill)[0];
80-
for (int j = 0; j < maxlen; j++) {
81-
REAL(ansp[j + rn])[k] = j < len ? dli[j] : dfill;
95+
for (int j = 0; j < num_cols; j++) {
96+
int idx = use_keep ? (keep_ptr[j] - 1) : j;
97+
REAL(ansp[j + rn])[k] = idx < len ? dli[idx] : dfill;
8298
}
8399
} break;
84100
case STRSXP: {
85101
const SEXP sfill = STRING_ELT(fill, 0);
86-
for (int j = 0; j < maxlen; j++) {
87-
SET_STRING_ELT(ansp[j + rn], k, j < len ? STRING_ELT(li, j) : sfill);
102+
for (int j = 0; j < num_cols; j++) {
103+
int idx = use_keep ? (keep_ptr[j] - 1) : j;
104+
SET_STRING_ELT(ansp[j + rn], k, idx < len ? STRING_ELT(li, idx) : sfill);
88105
}
89106
} break;
90107
case VECSXP: {
91108
const SEXP vfill = VECTOR_ELT(fill, 0);
92-
for (int j = 0; j < maxlen; j++) {
93-
SET_VECTOR_ELT(ansp[j + rn], k, j < len ? VECTOR_ELT(li, j) : vfill);
109+
for (int j = 0; j < num_cols; j++) {
110+
int idx = use_keep ? (keep_ptr[j] - 1) : j;
111+
SET_VECTOR_ELT(ansp[j + rn], k, idx < len ? VECTOR_ELT(li, idx) : vfill);
94112
}
95113
} break;
96114
default:

0 commit comments

Comments
 (0)