Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

4. `as.data.table()` now properly handles keys: specifying keys sets them, omitting keys preserves existing ones, and setting `key=NULL` clears them, [#6859](https://github.com/Rdatatable/data.table/issues/6859). Thanks @brookslogan for the report and @Mukulyadav2004 for the fix.

5. `as.data.table()` on `x` avoids an infinite loop if the output of the corresponding `as.data.frame()` method has the same class as the input, [#6874](https://github.com/Rdatatable/data.table/issues/6874). Concretely, we had `class(x) = c('foo', 'data.frame')` and `class(as.data.frame(x)) = c('foo', 'data.frame')`, so `as.data.frame.foo` wound up getting called repeatedly. Thanks @matschmitz for the report and @ben-schwen for the fix.

## NOTES

1. Continued work to remove non-API C functions, [#6180](https://github.com/Rdatatable/data.table/issues/6180). Thanks Ivan Krylov for the PRs and for writing a clear and concise guide about the R API: https://aitap.codeberg.page/R-api/.
Expand Down
7 changes: 6 additions & 1 deletion R/as.data.table.R
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,12 @@ as.data.table.list = function(x,

as.data.table.data.frame = function(x, keep.rownames=FALSE, key=NULL, ...) {
if (is.data.table(x)) return(as.data.table.data.table(x, key=key)) # S3 is weird, #6739. Also # nocov; this is tested in 2302.{2,3}, not sure why it doesn't show up in coverage.
if (!identical(class(x), "data.frame")) return(as.data.table(as.data.frame(x), keep.rownames=keep.rownames, key=key, ...))
if (!identical(class(x), "data.frame")) {
class_orig = class(x)
x = as.data.frame(x)
if (identical(class(x), class_orig)) setattr(x, "class", "data.frame") # cater for cases when as.data.frame can generate a loop #6874
return(as.data.table.data.frame(x, keep.rownames=keep.rownames, key=key, ...))
}
Comment on lines +218 to +223
Copy link
Member

Choose a reason for hiding this comment

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

What I had in mind is even simpler:

Suggested change
if (!identical(class(x), "data.frame")) {
class_orig = class(x)
x = as.data.frame(x)
if (identical(class(x), class_orig)) setattr(x, "class", "data.frame") # cater for cases when as.data.frame can generate a loop #6874
return(as.data.table.data.frame(x, keep.rownames=keep.rownames, key=key, ...))
}
if (!identical(class(x), "data.frame")) return(as.data.table.data.frame(as.data.frame(x), keep.rownames=keep.rownames, key=key, ...))

Copy link
Member Author

Choose a reason for hiding this comment

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

but this would put us back to the infinity recursion?

Copy link
Member

Choose a reason for hiding this comment

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

true true. for my suggestion we'd have to separate the rest of the body into an implementation function and call that to skip the input check at the top. not sure it's that beneficial actually.

if (!isFALSE(keep.rownames)) {
# can specify col name to keep.rownames, #575; if it's the same as key,
# kludge it to 'rn' since we only apply the new name afterwards, #4468
Expand Down
6 changes: 6 additions & 0 deletions inst/tests/S4.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,9 @@ setMethod(`%foo%`, c("Date", "CustomDurationClass"), function (e1, e2) e1 - e2@.
test(8, as.IDate("2025-03-01") %foo% CustomDurationClass(1), as.IDate("2025-02-28"))
removeGeneric("%foo%")
removeClass("CustomDurationClass")

# data.table(s4) #6874 should work
s4cl = setClass("s4cl", slots=list(x="integer"))
DT = setalloccol(structure(list(a=new("s4cl", x=1L)), row.names=c(NA, -1L), class=c("data.table", "data.frame")))
test(9, data.table(a=s4cl(x=1L)), DT)
removeClass("s4cl")
5 changes: 5 additions & 0 deletions inst/tests/tests.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -21104,3 +21104,8 @@ DT = data.table(a = 1:5, b = 1:5, x = 1:5)
test(2309.06, key(as.data.table(DT, key="a")), "a")
test(2309.07, key(as.data.table(DT)), NULL)
test(2309.08, key(as.data.table(DT, key=NULL)), NULL)

# as.data.frame(x) does not reset class(x) to "data.frame" #6874
as.data.frame.no.reset = function(x) x
DF = structure(list(a = 1:2), class = c("data.frame", "no.reset"), row.names = c(NA, -2L))
test(2310.01, as.data.table(DF), data.table(a=1:2))
2 changes: 1 addition & 1 deletion src/assign.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ SEXP setdt_nrows(SEXP x)
}
len_xi = INTEGER(dim_xi)[0];
} else {
len_xi = LENGTH(xi);
len_xi = length(xi);
}
if (!base_length) {
base_length = len_xi;
Expand Down
Loading