Nothing
#' @title Extract features from functional data.
#'
#' @description
#' Extract non-functional features from functional features using various methods.
#'
#' The function [extractFDAFeatures] performs the extraction for all functional features
#' via the methods specified in `feat.methods` and transforms all mentioned functional
#' (matrix) features into regular data.frame columns.
#' Additionally, a \dQuote{`extractFDAFeatDesc`} object
#' which contains learned coefficients and other helpful data for
#' re-extraction during the predict-phase is returned. This can be used with
#' [reextractFDAFeatures] in order to extract features during the prediction phase.
#'
#' @details
#' The description object contains these slots:
#' * target (`character`): See argument.
#' * coln (`character`): Colum names of data.
#' * fd.cols (`character`): Functional feature names.
#' * extractFDAFeat (`list`): Contains `feature.methods` and relevant
#' parameters for reextraction.
#'
#' @param obj ([Task] | [data.frame])\cr
#' Task or data.frame to extract functional features from.
#' Must contain functional features as matrix columns.
#' @param target (`character(1)`)\cr
#' Task target column. Only necessary for data.frames
#' Default is `character(0)`.
#' @param feat.methods (named [list])\cr
#' List of functional features along with the desired methods for each functional feature.
#' \dQuote{all} applies the [extractFDAFeatures] method to each
#' functional feature.
#' Names of `feat.methods` must match column names of functional features.
#' Available feature extraction methods are available under family `fda_featextractor`.
#' Specifying a functional feature multiple times with different extraction methods allows
#' for the extraction of different features from the same functional.
#' Default is [list()] which does nothing.
#' @param ... (any)\cr
#' Further hyperparameters passed on to the `feat.methods` specified above.
#' @return ([list])
#' - data | task ([data.frame] | [Task]): Extracted features, same type as obj.
#' - desc (`extracFDAFeatDesc`): Description object. See description for details.
#' @family fda
#' @export
#' @examples
#' \dontshow{ if (requireNamespace("wavelets")) \{ }
#' df = data.frame(x = matrix(rnorm(24), ncol = 8), y = factor(c("a", "a", "b")))
#' fdf = makeFunctionalData(df, fd.features = list(x1 = 1:4, x2 = 5:8), exclude.cols = "y")
#' task = makeClassifTask(data = fdf, target = "y")
#' extracted = extractFDAFeatures(task,
#' feat.methods = list("x1" = extractFDAFourier(), "x2" = extractFDAWavelets(filter = "haar")))
#' print(extracted$task)
#' reextractFDAFeatures(task, extracted$desc)
#' \dontshow{ \} }
extractFDAFeatures = function(obj, target = character(0L), feat.methods = list(), ...) {
assertList(feat.methods)
UseMethod("extractFDAFeatures")
}
#' @export
extractFDAFeatures.data.frame = function(obj, target = character(0L), feat.methods = list(), ...) {
fdf = getFunctionalFeatures(obj)
assertDataFrame(fdf, min.cols = 1L)
assertSubset(unique(names(feat.methods)), choices = c(names(fdf), "all"))
assertCharacter(target)
# If the same transform should be applied to all features, rep method and name accordingly
# "all" needs to be first list name.
all.fds = which(names(feat.methods) == "all")
if (length(all.fds) > 0L) {
methods = setNames(rep(feat.methods[all.fds], ncol(fdf)), rep(names(fdf), each = length(feat.methods[all.fds])))
feat.methods[all.fds] = NULL
feat.methods = c(feat.methods, methods)
}
desc = makeS3Obj("extractFDAFeatDesc",
target = target,
coln = colnames(obj),
fd.cols = NULL,
extractFDAFeat = namedList(names(feat.methods))
)
# cleanup empty list items
feat.methods = Filter(Negate(is.null), feat.methods)
# Treat method parameters:
# Overwrite the par.vals from ... so it is set correctly during tuning
feat.args = list(...)
desc$extractFDAFeat = Map(function(x) {
if (!is.null(x$par.set)) {
# Only set relevant params
feat.args = feat.args[names(feat.args) %in% getParamIds(x$par.set)]
}
if (length(feat.args) > 0) {
# Overwrite args
x$args = feat.args
}
return(x)
}, feat.methods)
# Subset fd.cols accordingly
desc$fd.cols = names(desc$extractFDAFeat)
# Apply function from x to all functional features and return as list of
# lists for each functional feature.
# The "learn" function only learns from the training data and returns the "extraction model".
extracts = Map(function(x, fd.col) {
reextract = x$reextract
# Learn an "extraction model which is a trained algo for extracting from train data.
extractor.vals = do.call(x$learn, c(x$args, list(data = obj, target = target, col = fd.col)))
list("reextract" = reextract, "extractor.vals" = extractor.vals)
}, x = desc$extractFDAFeat, fd.col = desc$fd.cols)
# Append Info relevant for reextraction to desc
desc$extractFDAFeat = extracts
extracted = reextractFDAFeatures(obj, desc)
list(data = extracted, desc = desc)
}
#' @export
extractFDAFeatures.Task = function(obj, target = character(0L), feat.methods = list(), ...) {
stopifnot((hasFunctionalFeatures(obj)))
data = getTaskData(obj, functionals.as = "matrix")
target = getTaskTargetNames(obj)
# Extract features from data
extracted = extractFDAFeatures.data.frame(obj = data, target = target, feat.methods = feat.methods, ...)
# And change data so it only contains non-functional features
task = changeData(obj, extracted$data)
list(task = task, desc = extracted$desc)
}
#' @export
print.extractFDAFeatDesc = function(x, ...) {
catf("Extraction of features from functional data:")
catf("Target: %s", collapse(x$target))
catf("Remaining functional Features: %i after extraction on %i functional features",
length(x$fd.cols), length(x$extractFDAFeat))
}
#' Re-extract features from a data set
#'
#' @description
#' This function accepts a data frame or a task and an extractFDAFeatDesc
#' (a FDA feature extraction description)
#' as returned by [extractFDAFeatures] to extract features
#' from previously unseen data.
#' @param obj ([Task] | [data.frame])\cr
#' Task or data.frame to extract functional features from. Must contain functional features
#' as matrix columns.
#' @param desc (`extractFDAFeatDesc`)\cr
#' FDAFeature extraction description as returned by [extractFDAFeatures]
#' @param ... (any)\cr
#' Further args passed on to methods.
#' @return [data.frame] or [Task] containing the extracted Features
#' @family extractFDAFeatures
#' @export
reextractFDAFeatures = function(obj, desc, ...) {
UseMethod("reextractFDAFeatures")
}
#' @export
reextractFDAFeatures.data.frame = function(obj, desc, ...) {
assertClass(desc, classes = "extractFDAFeatDesc")
# check for new columns
new.cols = names(which(names(obj) %nin% desc$coln))
if (length(new.cols)) {
stop("New columns (%s) found in data. Unable to extract.", collapse(new.cols))
}
# reextract features using reextractDescription and return
reextract = Map(
function(xn, x, fd.col) {
df = do.call(x$reextract, c(list(data = obj, target = desc$target, col = fd.col, vals = x$extractor.vals)))
# Check returned vals
if (!is.data.frame(df)) {
stop("feat.method needs to return a data.frame with one row per observation in the original data.")
} else if (nrow(df) != nrow(obj)) {
stop("feat.method needs to return a data.frame with one row per observation in the original data and equal nrow per column.")
}
return(df)
},
xn = names(desc$extractFDAFeat), x = desc$extractFDAFeat, fd.col = desc$fd.cols)
# cbind resulting columns. Use data.table to ensure proper naming.
df = as.data.frame(do.call(cbind, lapply(reextract, setDT)))
# Reappend target and non-functional features
keep.cols = setdiff(colnames(obj), desc$fd.cols)
data = cbind(df, obj[keep.cols])
return(data)
}
#' @export
reextractFDAFeatures.Task = function(obj, desc, ...) {
# get data and pass to extractor
df = getTaskData(obj, functionals.as = "matrix")
extracted = reextractFDAFeatures.data.frame(df, desc)
# Change data of task and return task
changeData(obj, extracted)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.