Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve helpfulness of warning message during on-assignment type coer… #2989

Merged
merged 5 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,28 @@

#### NOTES

1. The type coercion warning message has been improved, [#2989](https://github.com/Rdatatable/data.table/pull/2989). Thanks to @sarahbeeysian on [Twitter](https://twitter.com/sarahbeeysian/status/1021359529789775872) for highlighting. For example, given the follow statements:
```
DT = data.table(id=1:3)
DT[2, id:="foo"]
```
the warning message has changed from :
```
Coerced character RHS to integer to match the column's type. Either change the target column ['id'] to character first (by creating a new character vector length 3 (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to integer (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please.
```
to :
```
Coerced character RHS to integer to match the type of the target column (column 1 named 'id'). If the target column's type integer is correct, it's best for efficiency to avoid the coercion and create the RHS as type integer. To achieve that consider R's type postfix: typeof(0L) vs typeof(0), and typeof(NA) vs typeof(NA_integer_) vs typeof(NA_real_). You can wrap the RHS with as.integer() to avoid this warning, but that will still perform the coercion. If the target column's type is not correct, it's best to revisit where the DT was created and fix the column type there; e.g., by using colClasses= in fread(). Otherwise, you can change the column type now by plonking a new column (of the desired type) over the top of it; e.g. DT[, `id`:=as.character(`id`)]. If the RHS of := has nrow(DT) elements, then the assignment is called a column plonk and is the way to change a column's type. Column types can be observed with print(x,class=TRUE) and sapply(x,class).
```

Further, if a coercion from double to integer is performed, fractional data such as 3.14 is now detected and the truncation to 3 is warned about if and only if truncation has occurred.
```
DT = data.table(v=1:3)
DT[2, v:=3.14]
Warning message:
Coerced double RHS to integer to match the type of the target column (column 1 named 'v'). One or more RHS values contain fractions which have been lost; e.g. item 1 with value 3.140000 has been truncated to 3.
```


### Changes in v1.11.4 (on CRAN 27 May 2018)

Expand Down
78 changes: 39 additions & 39 deletions inst/tests/tests.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -857,16 +857,16 @@ test(299.03, truelength(DT)>length(DT)) # the := over-allocated, by 100 by def
# FR #2551 - old 299.3 and 299.5 are changed to include length(RHS) > 1 to issue the warning
DT[,c:=rep(42L,.N)] # plonk
test(299.04, DT, data.table(a=1:3, c=42L))
test(299.05, DT[2:3,c:=c(42, 42)], data.table(a=1:3,c=42L), warning="Coerced 'double' RHS to 'integer' to match the column's type.*length 3 [(]nrows of entire table[)]")
test(299.05, DT[2:3,c:=c(42, 42)], data.table(a=1:3,c=42L), warning="Coerced double RHS to integer.*column 2 named 'c'.*RHS.*no fractions.*more efficiently.*integer.*Consider.*L")
# FR #2551 - length(RHS) = 1 - no warning for type conversion
test(299.06, DT[2,c:=42], data.table(a=1:3,c=42L))
# also see tests 302 and 303. (Ok, new test file for fast assign would be tidier).
test(299.07, DT[,c:=rep(FALSE,nrow(DT))], data.table(a=1:3,c=FALSE)) # replace c column with logical
test(299.08, DT[2:3,c:=c(42,0)], data.table(a=1:3,c=c(FALSE,TRUE,FALSE)), warning="Coerced 'double' RHS to 'logical' to match the column's type.*length 3 [(]nrows of entire table[)]")
test(299.08, DT[2:3,c:=c(42,0)], data.table(a=1:3,c=c(FALSE,TRUE,FALSE)), warning="Coerced double RHS to logical.*column 2 named 'c'.*If the target column's type logical is correct")
# FR #2551 is now changed to fit in / fix bug #5442. Stricter warnings are in place now. Check tests 1294.1-34 below.
test(299.09, DT[2,c:=42], data.table(a=1:3,c=c(FALSE,TRUE,FALSE)), warning="Coerced 'double' RHS to 'logical' to match")
test(299.11, DT[2,c:=42L], data.table(a=1:3,c=c(FALSE,TRUE,FALSE)), warning="Coerced 'integer' RHS to 'logical' to match")
test(299.12, DT[2:3,c:=c(0L, 0L)], data.table(a=1:3,c=FALSE), warning="Coerced 'integer' RHS to 'logical' to match the column's type.*length 3 [(]nrows of entire table[)]")
test(299.09, DT[2,c:=42], data.table(a=1:3,c=c(FALSE,TRUE,FALSE)), warning="Coerced double RHS to logical to match")
test(299.11, DT[2,c:=42L], data.table(a=1:3,c=c(FALSE,TRUE,FALSE)), warning="Coerced integer RHS to logical to match")
test(299.12, DT[2:3,c:=c(0L, 0L)], data.table(a=1:3,c=FALSE), warning="Coerced integer RHS to logical to match the type of the target column.*If the target column's type logical is correct")


# Test bug fix #1468, combining i and by.
Expand Down Expand Up @@ -958,7 +958,7 @@ test(335, DT[,2:1]<-NULL, error="Attempt to assign to column")

DT = data.table(a=1:2, b=1:6)
test(336, DT[,z:=a/b], data.table(a=1:2,b=1:6,z=(1:2)/(1:6)))
test(337, DT[3:4,z:=a*b], data.table(a=1:2,b=1:6,z=c(1,1,3,8,1/5,2/6)), warning="Coerced 'integer' RHS to 'double' to match the colum")
test(337, DT[3:4,z:=a*b], data.table(a=1:2,b=1:6,z=c(1,1,3,8,1/5,2/6)), warning="Coerced integer RHS to double to match")


# test eval of LHS of := (using with=FALSE gives a warning here from v1.9.3)
Expand Down Expand Up @@ -1015,7 +1015,7 @@ test(355, DT[11:2010,f:=newlevels], data.table(f=factor(c(rep("000",10),newlevel
DT = data.table(f=c("a","b"),x=1:4)
# Test coercing factor to character column
test(355.5, DT[3,f:=factor("foo")], data.table(f=c("a","b","foo","b"),x=1:4))
test(355.6, DT[4,f:=factor("bar"),verbose=TRUE], data.table(f=c("a","b","foo","bar"),x=1:4), output="Coerced factor to character to match the column")
test(355.6, DT[4,f:=factor("bar"),verbose=TRUE], data.table(f=c("a","b","foo","bar"),x=1:4), output="Coerced factor RHS to character to match the column")


# See datatable-help post and NEWS item for 1.6.7
Expand Down Expand Up @@ -2235,7 +2235,7 @@ test(836, DT[,a:=as.integer(a)], data.table(a=INT(-1,0,1)))
test(837, DT[,a:=cbind(1,2)], data.table(a=c(1L,2L,1L)),
warning=c("2 column matrix RHS of := will be treated as one vector",
"Supplied 2 items to be assigned to 3 items.*recycled",
"Coerced 'double' RHS to 'integer' to match the column's type"))
"Coerced double RHS to integer to match.*column 1 named 'a'.*values contain no fractions"))
DT = data.table(a=1:3,b=1:6)
test(838, DT[,c:=scale(b), by=a][,c:=as.integer(1000*c)], data.table(a=1:3,b=1:6,c=rep(as.integer(1000*scale(1:2)), each=3)))

Expand Down Expand Up @@ -4681,38 +4681,38 @@ test(1293, ans1, ans2)
dt <- data.table(a=1:3, b=c(7,8,9), c=c(TRUE, NA, FALSE), d=as.list(4:6), e=c("a", "b", "c"))

test(1294.1, dt[, a := 1]$a, rep(1L, 3L))
test(1294.2, dt[, a := 1.5]$a, rep(1L, 3L), warning="Coerced 'double' RHS to 'integer' to match the column's type")
test(1294.2, dt[, a := 1.5]$a, rep(1L, 3L), warning="Coerced double RHS to integer.*column 1 named 'a'.*fractions which have been lost; e.g. item 1 with value 1.5.* truncated to 1.")
test(1294.3, dt[, a := NA]$a, rep(NA_integer_, 3L))
test(1294.4, dt[, a := "a"]$a, rep(NA_integer_, 3L),
warning=c("NAs introduced by coercion",
"Coerced 'character' RHS to 'integer' to match the column's type.*please"))
test(1294.5, dt[, a := list(list(1))]$a, rep(1L, 3L), warning="Coerced 'list' RHS to 'integer' to match the column's type")
"Coerced character RHS to integer.*create the RHS as type integer.*with as.integer.. to avoid this warning.*DT., `a`:=as.character.`a`.*"))
test(1294.5, dt[, a := list(list(1))]$a, rep(1L, 3L), warning="Coerced list RHS to integer to match.*column 1 named 'a'")
test(1294.6, dt[, a := list(1L)]$a, rep(1L, 3L))
test(1294.7, dt[, a := list(1)]$a, rep(1L, 3L))
test(1294.8, dt[, a := TRUE]$a, rep(1L, 3L), warning="Coerced 'logical' RHS to 'integer' to match the column's type")
test(1294.8, dt[, a := TRUE]$a, rep(1L, 3L), warning="Coerced logical RHS to integer")
test(1294.9, dt[, b := 1L]$b, rep(1,3))
test(1294.10, dt[, b := NA]$b, rep(NA_real_,3))
test(1294.11, dt[, b := "bla"]$b, rep(NA_real_, 3),
warning=c("NAs introduced by coercion",
"Coerced 'character' RHS to 'double' to match the column's type.*please"))
test(1294.12, dt[, b := list(list(1))]$b, rep(1,3), warning="Coerced 'list' RHS to 'double' to match the column's type")
test(1294.13, dt[, b := TRUE]$b, rep(1,3), warning="Coerced 'logical' RHS to 'double' to match the column's type")
"Coerced character RHS to double to match.*column 2 named 'b'.*DT[, `b`:=as.character(`b`)]"))
test(1294.12, dt[, b := list(list(1))]$b, rep(1,3), warning="Coerced list RHS to double")
test(1294.13, dt[, b := TRUE]$b, rep(1,3), warning="Coerced logical RHS to double")
test(1294.14, dt[, b := list(1)]$b, rep(1,3))
test(1294.15, dt[, c := 1]$c, rep(TRUE, 3), warning="Coerced 'double' RHS to 'logical' to match the column's type")
test(1294.16, dt[, c := 1L]$c, rep(TRUE, 3), warning="Coerced 'integer' RHS to 'logical' to match the column's type")
test(1294.15, dt[, c := 1]$c, rep(TRUE, 3), warning="Coerced double RHS to logical")
test(1294.16, dt[, c := 1L]$c, rep(TRUE, 3), warning="Coerced integer RHS to logical")
test(1294.17, dt[, c := NA]$c, rep(NA, 3))
test(1294.18, dt[, c := list(1)]$c, rep(TRUE, 3), warning="Coerced 'double' RHS to 'logical' to match the column's type")
test(1294.19, dt[, c := list(list(1))]$c, rep(TRUE, 3), warning="Coerced 'list' RHS to 'logical' to match the column's type")
test(1294.20, dt[, c := "bla"]$c, rep(NA, 3), warning="Coerced 'character' RHS to 'logical' to match the column's type")
test(1294.21, dt[, d := 1]$d, rep(list(1), 3), warning="Coerced 'double' RHS to 'list' to match the column's type")
test(1294.22, dt[, d := 1L]$d, rep(list(1L), 3), warning="Coerced 'integer' RHS to 'list' to match the column's type")
test(1294.23, dt[, d := TRUE]$d, rep(list(TRUE), 3), warning="Coerced 'logical' RHS to 'list' to match the column's type")
test(1294.24, dt[, d := "bla"]$d, rep(list("bla"), 3), warning="Coerced 'character' RHS to 'list' to match the column's type")
test(1294.18, dt[, c := list(1)]$c, rep(TRUE, 3), warning="Coerced double RHS to logical")
test(1294.19, dt[, c := list(list(1))]$c, rep(TRUE, 3), warning="Coerced list RHS to logical")
test(1294.20, dt[, c := "bla"]$c, rep(NA, 3), warning="Coerced character RHS to logical")
test(1294.21, dt[, d := 1]$d, rep(list(1), 3), warning="Coerced double RHS to list")
test(1294.22, dt[, d := 1L]$d, rep(list(1L), 3), warning="Coerced integer RHS to list")
test(1294.23, dt[, d := TRUE]$d, rep(list(TRUE), 3), warning="Coerced logical RHS to list")
test(1294.24, dt[, d := "bla"]$d, rep(list("bla"), 3), warning="Coerced character RHS to list")
test(1294.25, dt[, d := list(list(1))]$d, rep(list(1), 3))
test(1294.26, dt[, e := 1]$e, rep("1", 3), warning="Coerced 'double' RHS to 'character' to match the column's type")
test(1294.27, dt[, e := 1L]$e, rep("1", 3), warning="Coerced 'integer' RHS to 'character' to match the column's type")
test(1294.28, dt[, e := TRUE]$e, rep("TRUE", 3), warning="Coerced 'logical' RHS to 'character' to match the column's type")
test(1294.29, dt[, e := list(list(1))]$e, rep("1", 3), warning="Coerced 'list' RHS to 'character' to match the column's type")
test(1294.26, dt[, e := 1]$e, rep("1", 3), warning="Coerced double RHS to character")
test(1294.27, dt[, e := 1L]$e, rep("1", 3), warning="Coerced integer RHS to character")
test(1294.28, dt[, e := TRUE]$e, rep("TRUE", 3), warning="Coerced logical RHS to character")
test(1294.29, dt[, e := list(list(1))]$e, rep("1", 3), warning="Coerced list RHS to character")
test(1294.30, dt[, e := "bla"]$e, rep("bla", 3))
test(1294.31, dt[, e := list("bla2")]$e, rep("bla2", 3))

Expand Down Expand Up @@ -6718,19 +6718,19 @@ test(1513, setkey(as.data.table(X), a), setDT(X, key="a"))

# Adding tests for `isReallyReal`
x = as.numeric(sample(10))
test(1514.1, isReallyReal(x), FALSE)
test(1514.1, isReallyReal(x), 0L)
x = as.numeric(sample(c(1:5, NA)))
test(1514.2, isReallyReal(x), FALSE) # NAs are handled properly
x = as.numeric(sample(c(1:2, NaN, NA)))
test(1514.3, isReallyReal(x), TRUE)
x = as.numeric(sample(c(1:2, Inf, NA)))
test(1514.4, isReallyReal(x), TRUE)
x = as.numeric(sample(c(1:2, -Inf, NA)))
test(1514.5, isReallyReal(x), TRUE)
x = as.numeric(runif(2))
test(1514.6, isReallyReal(x), TRUE)
test(1514.2, isReallyReal(x), 0L) # NAs in numeric can be coerced to integer NA without loss
x = c(1:2, NaN, NA)
test(1514.3, isReallyReal(x), 3L)
x = c(1:2, Inf, NA)
test(1514.4, isReallyReal(x), 3L)
x = c(1:2, -Inf, NA)
test(1514.5, isReallyReal(x), 3L)
x = runif(2)
test(1514.6, isReallyReal(x), 1L)
x = numeric()
test(1514.7, isReallyReal(x), FALSE)
test(1514.7, isReallyReal(x), 0L)
test(1514.8, isReallyReal(9L), error="x must be of type double")

# #1091
Expand Down
38 changes: 25 additions & 13 deletions src/assign.c
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,6 @@ SEXP assign(SEXP dt, SEXP rows, SEXP cols, SEXP newcolnames, SEXP values, SEXP v
SEXP targetcol, RHS, names, nullint, thisvalue, thisv, targetlevels, newcol, s, colnam, class, tmp, colorder, key, index, a, assignedNames, indexNames;
SEXP bindingIsLocked = getAttrib(dt, install(".data.table.locked"));
Rboolean verbose = LOGICAL(verb)[0], anytodelete=FALSE, isDataTable=FALSE;
char *s1, *s2, *s3, *s4, *s5;
const char *c1, *tc1, *tc2;
int *buf, k=0, newKeyLength, indexNo;
size_t size; // must be size_t otherwise overflow later in memcpy
Expand Down Expand Up @@ -593,21 +592,34 @@ SEXP assign(SEXP dt, SEXP rows, SEXP cols, SEXP newcolnames, SEXP values, SEXP v
if (isString(targetcol) && isFactor(thisvalue)) {
PROTECT(RHS = asCharacterFactor(thisvalue));
protecti++;
if (verbose) Rprintf("Coerced factor to character to match the column's type (coercion is inefficient)\n"); // TO DO: datatable.pedantic would turn this into warning
if (verbose) Rprintf("Coerced factor RHS to character to match the column's type. Avoid this coercion if possible, for efficiency, by creating RHS as type character.\n");
// TO DO: datatable.pedantic could turn this into warning
} else {
PROTECT(RHS = coerceVector(thisvalue,TYPEOF(targetcol)));
protecti++;
char *s1 = (char *)type2char(TYPEOF(targetcol));
char *s2 = (char *)type2char(TYPEOF(thisvalue));
// FR #2551, added test for equality between RHS and thisvalue to not provide the warning when length(thisvalue) == 1
if ( length(thisvalue) == 1 && TYPEOF(RHS) != VECSXP && TYPEOF(thisvalue) != VECSXP && (
(isReal(thisvalue) && isInteger(targetcol) && REAL(thisvalue)[0] == INTEGER(RHS)[0]) ||
(isLogical(thisvalue) && LOGICAL(thisvalue)[0] == NA_LOGICAL) ||
(isReal(RHS) && isInteger(thisvalue)) )) {
;
if ( length(thisvalue)==1 && TYPEOF(RHS)!=VECSXP && TYPEOF(thisvalue)!=VECSXP && (
( isReal(thisvalue) && isInteger(targetcol) && REAL(thisvalue)[0]==INTEGER(RHS)[0] ) || // DT[,intCol:=4] rather than DT[,intCol:=4L]
( isLogical(thisvalue) && LOGICAL(thisvalue)[0] == NA_LOGICAL ) || // DT[,intCol:=NA]
( isReal(targetcol) && isInteger(thisvalue) ) )) {
if (verbose) Rprintf("Coerced length-1 RHS from %s to %s to match column's type.%s If this assign is happening a lot inside a loop, in particular via set(), then it may be worth avoiding this coercion by using R's type postfix on the value being assigned; e.g. typeof(0) vs typeof(0L), and typeof(NA) vs typeof(NA_integer_) vs typeof(NA_real_).\n", s2, s1,
isInteger(targetcol) && isReal(thisvalue) ? "No precision was lost. " : "");
// TO DO: datatable.pedantic could turn this into warning
} else {
if (isReal(thisvalue) && isInteger(targetcol)) {
int w = INTEGER(isReallyReal(thisvalue))[0]; // first fraction present (1-based), 0 if none
if (w>0) {
warning("Coerced double RHS to integer to match the type of the target column (column %d named '%s'). One or more RHS values contain fractions which have been lost; e.g. item %d with value %f has been truncated to %d.",
coln+1, CHAR(STRING_ELT(names, coln)), w, REAL(thisvalue)[w-1], INTEGER(RHS)[w-1]);
} else {
warning("Coerced double RHS to integer to match the type of the target column (column %d named '%s'). The RHS values contain no fractions so would be more efficiently created as integer. Consider using R's 'L' postfix (typeof(0L) vs typeof(0)) to create constants as integer and avoid this warning. Wrapping the RHS with as.integer() will avoid this warning too but it's better if possible to create the RHS as integer in the first place so that the cost of the coercion can be avoided.", coln+1, CHAR(STRING_ELT(names, coln)));
}
} else {
s1 = (char *)type2char(TYPEOF(targetcol));
s2 = (char *)type2char(TYPEOF(thisvalue));
if (isReal(thisvalue)) s3="; may have truncated precision"; else s3="";
warning("Coerced '%s' RHS to '%s' to match the column's type%s. Either change the target column ['%s'] to '%s' first (by creating a new '%s' vector length %d (nrows of entire table) and assign that; i.e. 'replace' column), or coerce RHS to '%s' (e.g. 1L, NA_[real|integer]_, as.*, etc) to make your intent clear and for speed. Or, set the column type correctly up front when you create the table and stick to it, please.", s2, s1, s3, CHAR(STRING_ELT(names, coln)), s2, s2, LENGTH(VECTOR_ELT(dt,0)), s1);
warning("Coerced %s RHS to %s to match the type of the target column (column %d named '%s'). If the target column's type %s is correct, it's best for efficiency to avoid the coercion and create the RHS as type %s. To achieve that consider R's type postfix: typeof(0L) vs typeof(0), and typeof(NA) vs typeof(NA_integer_) vs typeof(NA_real_). You can wrap the RHS with as.%s() to avoid this warning, but that will still perform the coercion. If the target column's type is not correct, it's best to revisit where the DT was created and fix the column type there; e.g., by using colClasses= in fread(). Otherwise, you can change the column type now by plonking a new column (of the desired type) over the top of it; e.g. DT[, `%s`:=as.%s(`%s`)]. If the RHS of := has nrow(DT) elements, then the assignment is called a column plonk and is the way to change a column's type. Column types can be observed with print(x,class=TRUE) and sapply(x,class).",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Column types can be observed with print(x,class=TRUE) and sapply(x,class).

Some users won't know types for non-atomic classes (integer = IDate, factor; double = Date). Could change sapply(x,class) to sapply(x,typeof), but I'm not sure how to change the reference to print. Maybe some rewording or an extra sentence could work. Alternately, maybe print could have another option, like verbose = TRUE that prints maximal info (somewhat substituting for str and similar to tables()):

library(data.table)
DT = data.table(id = 1:2, d = as.IDate(Sys.Date()) + 0:1)
setkey(DT, id)
setindex(DT, d, id)

print(DT, verbose = TRUE) # fake code
#       id          d
#    <int>     <IDat>
#               <int>
# 1:     1 2018-08-10
# 2:     2 2018-08-11
# 
# key: id
# indices: 
# - d, id

(... Also displaying new statistics from #2879)

Anyway, I guess the type/class distinction will only matter in rare cases, like...

library(data.table)
DT = data.table(id = 1:2, d = as.IDate(Sys.Date()) + 0:1)

DT[1, d := "1999-01-01"] 
# gets coerced to NA_integer_

DT[1, d := 1999-01-01] 
# worse, if forgetting quotes and class/type, it gets silently handled as an int

In contrast, sub-assigning a character to a factor works as expected.

s2, s1, coln+1, CHAR(STRING_ELT(names, coln)), s1, s1, s1, CHAR(STRING_ELT(names, coln)), s2, CHAR(STRING_ELT(names, coln)));
}
}
}
}
Expand Down Expand Up @@ -674,7 +686,7 @@ SEXP assign(SEXP dt, SEXP rows, SEXP cols, SEXP newcolnames, SEXP values, SEXP v
if (!*tc1) error("Internal error: index name ends with trailing __");
// check the position of the first appearance of an assigned column in the index.
// the new index will be truncated to this position.
s4 = (char*) malloc(strlen(c1) + 3);
char *s4 = (char*) malloc(strlen(c1) + 3);
if(s4 == NULL){
error("Internal error: Couldn't allocate memory for s4.");
}
Expand All @@ -684,7 +696,7 @@ SEXP assign(SEXP dt, SEXP rows, SEXP cols, SEXP newcolnames, SEXP values, SEXP v
newKeyLength = strlen(c1);
for(int i = 0; i < xlength(assignedNames); i++){
tc2 = CHAR(STRING_ELT(assignedNames, i));
s5 = (char*) malloc(strlen(tc2) + 5); //4 * '_' + \0
char *s5 = (char*) malloc(strlen(tc2) + 5); //4 * '_' + \0
if(s5 == NULL){
free(s4);
error("Internal error: Couldn't allocate memory for s5.");
Expand Down
1 change: 1 addition & 0 deletions src/data.table.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ unsigned long long i64twiddle(void *p, int i, int order);
unsigned long long (*twiddle)(void *, int, int);
SEXP forder(SEXP DT, SEXP by, SEXP retGrp, SEXP sortStrArg, SEXP orderArg, SEXP naArg);
bool need2utf8(SEXP x, int n);
SEXP isReallyReal(SEXP);

// reorder.c
SEXP reorder(SEXP x, SEXP order);
Expand Down
Loading