Skip to content

Commit a285934

Browse files
authored
Merge branch 'master' into hindi_j
2 parents 4e95d86 + a36caac commit a285934

File tree

14 files changed

+270
-93
lines changed

14 files changed

+270
-93
lines changed

.gitlab-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ test-lin-dev-clang-cran:
194194
- echo 'CFLAGS=-g -O2 -fno-common -Wall -Wvla -pedantic -fstack-protector-strong -D_FORTIFY_SOURCE=2' > ~/.R/Makevars
195195
- echo 'CXXFLAGS=-g -O2 -fno-common -Wall -Wvla -pedantic -fstack-protector-strong -D_FORTIFY_SOURCE=2' >> ~/.R/Makevars
196196
- *install-deps
197+
- clang-tidy -extra-arg=-I/usr/local/lib/R/include -checks='readability-inconsistent-declaration-parameter' src/*.c -- -std=c99
197198
- R CMD check --as-cran $(ls -1t data.table_*.tar.gz | head -n 1)
198199
- (! grep "warning:" data.table.Rcheck/00install.out)
199200
- >-

DESCRIPTION

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,7 @@ Authors@R: c(
9898
person("Christian", "Wia", role="ctb"),
9999
person("Elise", "Maigné", role="ctb"),
100100
person("Vincent", "Rocher", role="ctb"),
101-
person("Vijay", "Lulla", role="ctb")
101+
person("Vijay", "Lulla", role="ctb"),
102+
person("Aljaž", "Sluga", role="ctb"),
103+
person("Bill", "Evans", role="ctb")
102104
)

NAMESPACE

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ if (getRversion() >= "4.0.0") {
104104
# version of R (and that is checked in .onLoad with error if not).
105105
export(.rbind.data.table) # only export in R<4.0.0 where it is still used; R-devel now detects it is missing doc, #5600
106106
}
107+
if (getRversion() >= "3.6.0") S3method(knitr::knit_print, data.table) # else manual delayed registration from the onLoad hook
107108
S3method(dim, data.table)
108109
S3method(dimnames, data.table)
109110
S3method("dimnames<-", data.table)
@@ -201,6 +202,7 @@ S3method(format_col, POSIXct)
201202
S3method(format_col, expression)
202203
export(format_list_item)
203204
S3method(format_list_item, default)
205+
S3method(format_list_item, data.frame)
204206

205207
export(fdroplevels, setdroplevels)
206208
S3method(droplevels, data.table)

NEWS.md

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ rowwiseDT(
6565

6666
4. `patterns()` in `melt()` combines correctly with user-defined `cols=`, which can be useful to specify a subset of columns to reshape without having to use a regex, for example `patterns("2", cols=c("y1", "y2"))` will only give `y2` even if there are other columns in the input matching `2`, [#6498](https://github.com/Rdatatable/data.table/issues/6498). Thanks to @hongyuanjia for the report, and to @tdhock for the PR.
6767

68+
5. `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.
69+
6870
## BUG FIXES
6971

7072
1. `fwrite()` respects `dec=','` for timestamp columns (`POSIXct` or `nanotime`) with sub-second accuracy, [#6446](https://github.com/Rdatatable/data.table/issues/6446). Thanks @kav2k for pointing out the inconsistency and @MichaelChirico for the PR.
@@ -107,17 +109,27 @@ rowwiseDT(
107109
108110
11. `tables()` now returns the correct size for data.tables over 2GiB, [#6607](https://github.com/Rdatatable/data.table/issues/6607). Thanks to @vlulla for the report and the PR.
109111
112+
12. Joins on multiple columns, such as `x[y, on=c("x1==y1", "x2==y1")]`, could fail during implicit type coercions if `x1` and `x2` had different but still compatible types, [#6602](https://github.com/Rdatatable/data.table/issues/6602). This was particularly unexpected when columns `x1`, `x2`, and `y1` were all of the same class, e.g. `Date`, but differed in their underlying storage types. Thanks to Benjamin Schwendinger for the report and the fix.
113+
114+
13. `rbindlist(l, use.names=TRUE)` can now handle different encodings for the column names in different entries of `l`, [#5452](https://github.com/Rdatatable/data.table/issues/5452). Thanks to @MEO265 for the report, and Benjamin Schwendinger for the fix.
115+
116+
14. Added a `data.frame` method for `format_list_item()` to fix error printing data.tables with columns containing 1-column data.frames, [#6592](https://github.com/Rdatatable/data.table/issues/6592). Thanks to @r2evans for the bug report and fix.
117+
118+
15. The auto-printing suppression in `knitr` documents is now done by implementing a method for `knit_print` instead of looking up the call stack, [#6589](https://github.com/Rdatatable/data.table/pull/6589). Thanks to @jangorecki for the report [#6509](https://github.com/Rdatatable/data.table/issues/6509) and @aitap for the fix.
119+
110120
## NOTES
111121
112-
1. Tests run again when some Suggests packages are missing, [#6411](https://github.com/Rdatatable/data.table/issues/6411). Thanks @aadler for the note and @MichaelChirico for the fix.
122+
1. There is a new vignette on joins! See `vignette("datatable-joins")`. Thanks to Angel Feliz for authoring it! Feedback welcome. This vignette has been highly requested since 2017: [#2181](https://github.com/Rdatatable/data.table/issues/2181).
123+
124+
2. Tests run again when some Suggests packages are missing, [#6411](https://github.com/Rdatatable/data.table/issues/6411). Thanks @aadler for the note and @MichaelChirico for the fix.
113125
114-
2. Some grouping operations run much faster under `verbose=TRUE`, [#6286](https://github.com/Rdatatable/data.table/issues/6286). Thanks @joshhwuu for the report and fix. This overhead was not present on Windows. As a rule, users should expect `verbose=TRUE` operations to run more slowly, as extra statistics might be calculated as part of the report; here was a case where the overhead was particularly high and the fix was particularly easy.
126+
3. Some grouping operations run much faster under `verbose=TRUE`, [#6286](https://github.com/Rdatatable/data.table/issues/6286). Thanks @joshhwuu for the report and fix. This overhead was not present on Windows. As a rule, users should expect `verbose=TRUE` operations to run more slowly, as extra statistics might be calculated as part of the report; here was a case where the overhead was particularly high and the fix was particularly easy.
115127
116-
3. `set()` and `:=` now provide some extra guidance for common incorrect approaches to assigning `NULL` to some rows of a list column. The correct way is to put `list(list(NULL))` on the RHS of `:=` (or `.(.(NULL))` for short). Thanks to @MichaelChirico for the suggestion and @Nj221102 for the implementation.
128+
4. `set()` and `:=` now provide some extra guidance for common incorrect approaches to assigning `NULL` to some rows of a list column. The correct way is to put `list(list(NULL))` on the RHS of `:=` (or `.(.(NULL))` for short). Thanks to @MichaelChirico for the suggestion and @Nj221102 for the implementation.
117129
118-
4. Improved the error message when trying to write code like `DT[, ":="(a := b, c := d)]` (which should be `DT[, ":="(a = b, c = d)]`), [#5296](https://github.com/Rdatatable/data.table/issues/5296). Thanks @MichaelChirico for the suggestion & fix.
130+
5. Improved the error message when trying to write code like `DT[, ":="(a := b, c := d)]` (which should be `DT[, ":="(a = b, c = d)]`), [#5296](https://github.com/Rdatatable/data.table/issues/5296). Thanks @MichaelChirico for the suggestion & fix.
119131
120-
5. `measurev()` was implemented and documented in v1.15.0, for use within `melt()`, and it is now exported (dependent packages can now use without a NOTE from CRAN check).
132+
6. `measurev()` was implemented and documented in v1.15.0, for use within `melt()`, and it is now exported (dependent packages can now use without a NOTE from CRAN check).
121133
122134
# data.table [v1.16.2](https://github.com/Rdatatable/data.table/milestone/35) (9 October 2024)
123135

R/bmerge.R

Lines changed: 90 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,25 @@
11

2+
3+
mergeType = function(x) {
4+
ans = typeof(x)
5+
if (ans=="integer") { if (is.factor(x)) ans = "factor" }
6+
else if (ans=="double") { if (inherits(x, "integer64")) ans = "integer64" }
7+
# do not call isReallyReal(x) yet because i) if both types are double we don't need to coerce even if one or both sides
8+
# are int-as-double, and ii) to save calling it until we really need it
9+
ans
10+
}
11+
12+
cast_with_atts = function(x, as.f) {
13+
ans = as.f(x)
14+
if (!is.null(attributes(x))) attributes(ans) = attributes(x)
15+
ans
16+
}
17+
18+
coerce_col = function(dt, col, from_type, to_type, from_name, to_name, verbose_msg=NULL) {
19+
if (!is.null(verbose_msg)) catf(verbose_msg, from_type, from_name, to_type, to_name, domain=NULL)
20+
set(dt, j=col, value=cast_with_atts(dt[[col]], match.fun(paste0("as.", to_type))))
21+
}
22+
223
bmerge = function(i, x, icols, xcols, roll, rollends, nomatch, mult, ops, verbose)
324
{
425
callersi = i
@@ -25,95 +46,110 @@ bmerge = function(i, x, icols, xcols, roll, rollends, nomatch, mult, ops, verbos
2546

2647
supported = c(ORDERING_TYPES, "factor", "integer64")
2748

28-
getClass = function(x) {
29-
ans = typeof(x)
30-
if (ans=="integer") { if (is.factor(x)) ans = "factor" }
31-
else if (ans=="double") { if (inherits(x, "integer64")) ans = "integer64" }
32-
# do not call isReallyReal(x) yet because i) if both types are double we don't need to coerce even if one or both sides
33-
# are int-as-double, and ii) to save calling it until we really need it
34-
ans
35-
}
36-
3749
if (nrow(i)) for (a in seq_along(icols)) {
3850
# - check that join columns have compatible types
3951
# - do type coercions if necessary on just the shallow local copies for the purpose of join
4052
# - handle factor columns appropriately
4153
# Note that if i is keyed, if this coerces i's key gets dropped by set()
42-
ic = icols[a]
43-
xc = xcols[a]
44-
xclass = getClass(x[[xc]])
45-
iclass = getClass(i[[ic]])
46-
xname = paste0("x.", names(x)[xc])
47-
iname = paste0("i.", names(i)[ic])
48-
if (!xclass %chin% supported) stopf("%s is type %s which is not supported by data.table join", xname, xclass)
49-
if (!iclass %chin% supported) stopf("%s is type %s which is not supported by data.table join", iname, iclass)
50-
if (xclass=="factor" || iclass=="factor") {
54+
icol = icols[a]
55+
xcol = xcols[a]
56+
x_merge_type = mergeType(x[[xcol]])
57+
i_merge_type = mergeType(i[[icol]])
58+
xname = paste0("x.", names(x)[xcol])
59+
iname = paste0("i.", names(i)[icol])
60+
if (!x_merge_type %chin% supported) stopf("%s is type %s which is not supported by data.table join", xname, x_merge_type)
61+
if (!i_merge_type %chin% supported) stopf("%s is type %s which is not supported by data.table join", iname, i_merge_type)
62+
if (x_merge_type=="factor" || i_merge_type=="factor") {
5163
if (roll!=0.0 && a==length(icols))
5264
stopf("Attempting roll join on factor column when joining %s to %s. Only integer, double or character columns may be roll joined.", xname, iname)
53-
if (xclass=="factor" && iclass=="factor") {
65+
if (x_merge_type=="factor" && i_merge_type=="factor") {
5466
if (verbose) catf("Matching %s factor levels to %s factor levels.\n", iname, xname)
55-
set(i, j=ic, value=chmatch(levels(i[[ic]]), levels(x[[xc]]), nomatch=0L)[i[[ic]]]) # nomatch=0L otherwise a level that is missing would match to NA values
67+
set(i, j=icol, value=chmatch(levels(i[[icol]]), levels(x[[xcol]]), nomatch=0L)[i[[icol]]]) # nomatch=0L otherwise a level that is missing would match to NA values
5668
next
5769
} else {
58-
if (xclass=="character") {
70+
if (x_merge_type=="character") {
5971
if (verbose) catf("Coercing factor column %s to type character to match type of %s.\n", iname, xname)
60-
set(i, j=ic, value=val<-as.character(i[[ic]]))
61-
set(callersi, j=ic, value=val) # factor in i joining to character in x will return character and not keep x's factor; e.g. for antaresRead #3581
72+
set(i, j=icol, value=val<-as.character(i[[icol]]))
73+
set(callersi, j=icol, value=val) # factor in i joining to character in x will return character and not keep x's factor; e.g. for antaresRead #3581
6274
next
63-
} else if (iclass=="character") {
75+
} else if (i_merge_type=="character") {
6476
if (verbose) catf("Matching character column %s to factor levels in %s.\n", iname, xname)
65-
newvalue = chmatch(i[[ic]], levels(x[[xc]]), nomatch=0L)
66-
if (anyNA(i[[ic]])) newvalue[is.na(i[[ic]])] = NA_integer_ # NA_character_ should match to NA in factor, #3809
67-
set(i, j=ic, value=newvalue)
77+
newvalue = chmatch(i[[icol]], levels(x[[xcol]]), nomatch=0L)
78+
if (anyNA(i[[icol]])) newvalue[is.na(i[[icol]])] = NA_integer_ # NA_character_ should match to NA in factor, #3809
79+
set(i, j=icol, value=newvalue)
6880
next
6981
}
7082
}
71-
stopf("Incompatible join types: %s (%s) and %s (%s). Factor columns must join to factor or character columns.", xname, xclass, iname, iclass)
83+
stopf("Incompatible join types: %s (%s) and %s (%s). Factor columns must join to factor or character columns.", xname, x_merge_type, iname, i_merge_type)
7284
}
73-
if (xclass == iclass) {
74-
if (verbose) catf("%s has same type (%s) as %s. No coercion needed.\n", iname, xclass, xname)
85+
# we check factors first to cater for the case when trying to do rolling joins on factors
86+
if (x_merge_type == i_merge_type) {
87+
if (verbose) catf("%s has same type (%s) as %s. No coercion needed.\n", iname, x_merge_type, xname)
7588
next
7689
}
77-
if (xclass=="character" || iclass=="character" ||
78-
xclass=="logical" || iclass=="logical" ||
79-
xclass=="factor" || iclass=="factor") {
80-
if (anyNA(i[[ic]]) && allNA(i[[ic]])) {
81-
if (verbose) catf("Coercing all-NA %s (%s) to type %s to match type of %s.\n", iname, iclass, xclass, xname)
82-
set(i, j=ic, value=match.fun(paste0("as.", xclass))(i[[ic]]))
90+
cfl = c("character", "logical", "factor")
91+
if (x_merge_type %chin% cfl || i_merge_type %chin% cfl) {
92+
msg = if(verbose) gettext("Coercing all-NA %s column %s to type %s to match type of %s.\n") else NULL
93+
if (anyNA(i[[icol]]) && allNA(i[[icol]])) {
94+
coerce_col(i, icol, i_merge_type, x_merge_type, iname, xname, msg)
8395
next
8496
}
85-
else if (anyNA(x[[xc]]) && allNA(x[[xc]])) {
86-
if (verbose) catf("Coercing all-NA %s (%s) to type %s to match type of %s.\n", xname, xclass, iclass, iname)
87-
set(x, j=xc, value=match.fun(paste0("as.", iclass))(x[[xc]]))
97+
if (anyNA(x[[xcol]]) && allNA(x[[xcol]])) {
98+
coerce_col(x, xcol, x_merge_type, i_merge_type, xname, iname, msg)
8899
next
89100
}
90-
stopf("Incompatible join types: %s (%s) and %s (%s)", xname, xclass, iname, iclass)
101+
stopf("Incompatible join types: %s (%s) and %s (%s)", xname, x_merge_type, iname, i_merge_type)
91102
}
92-
if (xclass=="integer64" || iclass=="integer64") {
103+
if (x_merge_type=="integer64" || i_merge_type=="integer64") {
93104
nm = c(iname, xname)
94-
if (xclass=="integer64") { w=i; wc=ic; wclass=iclass; } else { w=x; wc=xc; wclass=xclass; nm=rev(nm) } # w is which to coerce
105+
if (x_merge_type=="integer64") { w=i; wc=icol; wclass=i_merge_type; } else { w=x; wc=xcol; wclass=x_merge_type; nm=rev(nm) } # w is which to coerce
95106
if (wclass=="integer" || (wclass=="double" && !isReallyReal(w[[wc]]))) {
96107
if (verbose) catf("Coercing %s column %s%s to type integer64 to match type of %s.\n", wclass, nm[1L], if (wclass=="double") " (which contains no fractions)" else "", nm[2L])
97108
set(w, j=wc, value=bit64::as.integer64(w[[wc]]))
98109
} else stopf("Incompatible join types: %s is type integer64 but %s is type double and contains fractions", nm[2L], nm[1L])
99110
} else {
100111
# just integer and double left
101-
if (iclass=="double") {
102-
if (!isReallyReal(i[[ic]])) {
112+
ic_idx = which(icol == icols) # check if on is joined on multiple conditions, #6602
113+
if (i_merge_type=="double") {
114+
coerce_x = FALSE
115+
if (!isReallyReal(i[[icol]])) {
116+
coerce_x = TRUE
103117
# common case of ad hoc user-typed integers missing L postfix joining to correct integer keys
104118
# we've always coerced to int and returned int, for convenience.
105-
if (verbose) catf("Coercing double column %s (which contains no fractions) to type integer to match type of %s.\n", iname, xname)
106-
val = as.integer(i[[ic]])
107-
if (!is.null(attributes(i[[ic]]))) attributes(val) = attributes(i[[ic]]) # to retain Date for example; 3679
108-
set(i, j=ic, value=val)
109-
set(callersi, j=ic, value=val) # change the shallow copy of i up in [.data.table to reflect in the result, too.
110-
} else {
111-
if (verbose) catf("Coercing integer column %s to type double to match type of %s which contains fractions.\n", xname, iname)
112-
set(x, j=xc, value=as.double(x[[xc]]))
119+
if (length(ic_idx)>1L) {
120+
xc_idx = xcols[ic_idx]
121+
for (xb in xc_idx[which(vapply_1c(.shallow(x, xc_idx), mergeType) == "double")]) {
122+
if (isReallyReal(x[[xb]])) {
123+
coerce_x = FALSE
124+
break
125+
}
126+
}
127+
}
128+
if (coerce_x) {
129+
msg = if (verbose) gettext("Coercing %s column %s (which contains no fractions) to type %s to match type of %s.\n") else NULL
130+
coerce_col(i, icol, "double", "integer", iname, xname, msg)
131+
set(callersi, j=icol, value=i[[icol]]) # change the shallow copy of i up in [.data.table to reflect in the result, too.
132+
if (length(ic_idx)>1L) {
133+
xc_idx = xcols[ic_idx]
134+
for (xb in xc_idx[which(vapply_1c(.shallow(x, xc_idx), mergeType) == "double")]) {
135+
coerce_col(x, xb, "double", "integer", paste0("x.", names(x)[xb]), xname, msg)
136+
}
137+
}
138+
}
139+
}
140+
if (!coerce_x) {
141+
msg = if (verbose) gettext("Coercing %s column %s to type %s to match type of %s which contains fractions.\n") else NULL
142+
coerce_col(x, xcol, "integer", "double", xname, iname, msg)
113143
}
114144
} else {
115-
if (verbose) catf("Coercing integer column %s to type double for join to match type of %s.\n", iname, xname)
116-
set(i, j=ic, value=as.double(i[[ic]]))
145+
msg = if (verbose) gettext("Coercing %s column %s to type %s for join to match type of %s.\n") else NULL
146+
coerce_col(i, icol, "integer", "double", iname, xname, msg)
147+
if (length(ic_idx)>1L) {
148+
xc_idx = xcols[ic_idx]
149+
for (xb in xc_idx[which(vapply_1c(.shallow(x, xc_idx), mergeType) == "integer")]) {
150+
coerce_col(x, xb, "integer", "double", paste0("x.", names(x)[xb]), xname, msg)
151+
}
152+
}
117153
}
118154
}
119155
}

R/data.table.R

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2736,15 +2736,16 @@ setnames = function(x,old,new,skip_absent=FALSE) {
27362736
invisible(x)
27372737
}
27382738

2739-
setcolorder = function(x, neworder=key(x), before=NULL, after=NULL) # before/after #4358
2739+
setcolorder = function(x, neworder=key(x), before=NULL, after=NULL, skip_absent=FALSE) # before/after #4358
27402740
{
27412741
if (is.character(neworder))
27422742
check_duplicate_names(x)
27432743
if (!is.null(before) && !is.null(after))
27442744
stopf("Provide either before= or after= but not both")
27452745
if (length(before)>1L || length(after)>1L)
27462746
stopf("before=/after= accept a single column name or number, not more than one")
2747-
neworder = colnamesInt(x, neworder, check_dups=FALSE) # dups are now checked inside Csetcolorder below
2747+
neworder = colnamesInt(x, neworder, check_dups=FALSE, skip_absent=skip_absent) # dups are now checked inside Csetcolorder below
2748+
neworder = neworder[neworder != 0] # tests 498.11, 498.13 fail w/o this
27482749
if (length(before))
27492750
neworder = c(setdiff(seq_len(colnamesInt(x, before) - 1L), neworder), neworder)
27502751
if (length(after))

R/onLoad.R

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@
6666
lockBinding("rbind.data.frame",baseenv())
6767
}
6868
}
69+
if (session_r_version < "3.6.0") { # corresponds to S3method() directive in NAMESPACE
70+
# no delayed registration support for NAMESPACE; perform it manually
71+
if (isNamespaceLoaded("knitr")) {
72+
registerS3method("knit_print", "data.table", knit_print.data.table, envir = asNamespace("knitr"))
73+
}
74+
setHook(packageEvent("knitr", "onLoad"), function(...) {
75+
registerS3method("knit_print", "data.table", knit_print.data.table, envir = asNamespace("knitr"))
76+
})
77+
}
6978

7079
# Set options for the speed boost in v1.8.0 by avoiding 'default' arg of getOption(,default=)
7180
# 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

0 commit comments

Comments
 (0)