diff --git a/NEWS.md b/NEWS.md index 24a078836..4dff14591 100644 --- a/NEWS.md +++ b/NEWS.md @@ -26,6 +26,8 @@ - Type conversion support in GForce expressions (e.g., `sum(as.numeric(x))` will use GForce, saving the need to coerce `x` in a setup step) [#2934](https://github.com/Rdatatable/data.table/issues/2934) - Arithmetic operation support in GForce (e.g., `max(x) - min(x)` will use GForce on both `max(x)` and `min(x)`, saving the need to do the subtraction in a follow-up step) [#3815](https://github.com/Rdatatable/data.table/issues/3815) +4. Added support for dcast/melt of data.frames, Thanks @MichaelChirico for the suggestion and @manmita for the PR. [#7614](https://github.com/Rdatatable/data.table/issues/7614) + ### 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/fcast.R b/R/fcast.R index c11eb76d5..8beb94c24 100644 --- a/R/fcast.R +++ b/R/fcast.R @@ -12,7 +12,14 @@ dcast = function( data, formula, fun.aggregate = NULL, ..., margins = NULL, subset = NULL, fill = NULL, value.var = guess(data) ) { - UseMethod("dcast", data) + if (!is.data.table(data) && is.data.frame(data)){ + mc <- match.call() + mc[[1L]] <- as.name("dcast.data.table") + eval(mc, parent.frame()) + } + else { + UseMethod("dcast", data) + } } check_formula = function(formula, varnames, valnames, value.var.in.LHSdots, value.var.in.RHSdots) { @@ -119,7 +126,6 @@ aggregate_funs = function(funs, vals, sep="_", ...) { } dcast.data.table = function(data, formula, fun.aggregate = NULL, sep = "_", ..., margins = NULL, subset = NULL, fill = NULL, drop = TRUE, value.var = guess(data), verbose = getOption("datatable.verbose"), value.var.in.dots = FALSE, value.var.in.LHSdots = value.var.in.dots, value.var.in.RHSdots = value.var.in.dots) { - if (!is.data.table(data)) stopf("'%s' must be a data.table", "data") drop = as.logical(rep_len(drop, 2L)) if (anyNA(drop)) stopf("'drop' must be logical vector with no missing entries") if (!isTRUEorFALSE(value.var.in.dots)) diff --git a/R/fmelt.R b/R/fmelt.R index 85a8a641e..44cae2e6c 100644 --- a/R/fmelt.R +++ b/R/fmelt.R @@ -4,7 +4,14 @@ # redirection as well melt = function(data, ..., na.rm = FALSE, value.name = "value") { - UseMethod("melt", data) + if (!is.data.table(data) && is.data.frame(data)){ + mc <- match.call() + mc[[1L]] <- as.name("melt.data.table") + eval(mc, parent.frame()) + } + else { + UseMethod("melt", data) + } } patterns = function(..., cols=character(0L), ignore.case=FALSE, perl=FALSE, fixed=FALSE, useBytes=FALSE) { @@ -176,7 +183,6 @@ measurev = function(fun.list, sep="_", pattern, cols, multiple.keyword="value.na melt.data.table = function(data, id.vars, measure.vars, variable.name = "variable", value.name = "value", ..., na.rm = FALSE, variable.factor = TRUE, value.factor = FALSE, verbose = getOption("datatable.verbose")) { - if (!is.data.table(data)) stopf("'%s' must be a data.table", "data") for(type.vars in c("id.vars","measure.vars")){ sub.lang <- substitute({ if (missing(VAR)) VAR=NULL diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 1c7ab6837..34df83ddb 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -13043,8 +13043,8 @@ test(1953.2, melt(DT, id.vars = 'id', measure.vars = patterns(a = 'a', b = 'b', test(1953.3, melt(DT, id.vars = 'id', measure.vars = patterns(1L)), error = 'Input patterns must be of type character') setDF(DT) -test(1953.4, melt.data.table(DT, id.vars = 'id', measure.vars = 'a'), - error = "must be a data.table") +expected = data.table(id = rep(DT$id, 2), variable = factor(rep(c("a1", "a2"), each = 3)), value = c(DT$a1, DT$a2)) +test(1953.4, melt.data.table(DT, id.vars = "id", measure.vars = c("a1", "a2")), expected) # appearance order of two low-cardinality columns that were squashed in pr#3124 DT = data.table(A=INT(1,3,2,3,2), B=1:5) # respect groups in 1st column (3's and 2's) @@ -13390,7 +13390,8 @@ setnames(DT, 'V') test(1962.084, guess(DT), 'V', message = 'Using.*value column.*override') setDF(DT) -test(1962.085, dcast.data.table(DT), error = 'must be a data.table') +test(1962.085, guess(DT), 'V', + message = 'Using.*value column.*override') setDT(DT) test(1962.086, dcast(DT, a ~ a, drop = NA), error = "'drop' must be logical vector with no missing entries") @@ -21509,3 +21510,13 @@ setdroplevels(x) setdroplevels(y) test(2364.2, levels(x$a), levels(y$a)) rm(x, y) + +# test for data.frame reshape for melt +df_melt = data.frame(a = 1:2, b = 3:4) +dt_melt = data.table(a = 1:2, b = 3:4) +test(2365.1, melt(df_melt, id.vars=1:2), melt(dt_melt, id.vars=1:2)) + +# test for data.frame reshape for dcast +df_dcast = data.frame(a = c("x", "y"), b = 1:2, v = 3:4) +dt_dcast = data.table(a = c("x", "y"), b = 1:2, v = 3:4) +test(2365.2, dcast(df_dcast, a ~ b, value.var = "v"), dcast(dt_dcast, a ~ b, value.var = "v")) \ No newline at end of file