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

Tdata in tealdata@main #138

Closed
wants to merge 9 commits into from
Closed
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
10 changes: 10 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ S3method(get_code,TealDataAbstract)
S3method(get_code,TealDataset)
S3method(get_code,TealDatasetConnector)
S3method(get_code,default)
S3method(get_code,tdata)
S3method(get_dataname,TealDataAbstract)
S3method(get_dataname,TealDataset)
S3method(get_dataname,TealDatasetConnector)
Expand All @@ -20,11 +21,15 @@ S3method(get_dataset_label,TealDatasetConnector)
S3method(get_datasets,TealDataAbstract)
S3method(get_datasets,TealDataset)
S3method(get_datasets,TealDatasetConnector)
S3method(get_join_keys,default)
S3method(get_join_keys,tdata)
S3method(get_key_duplicates,TealDataset)
S3method(get_key_duplicates,data.frame)
S3method(get_keys,TealDataAbstract)
S3method(get_keys,TealDataset)
S3method(get_keys,TealDatasetConnector)
S3method(get_metadata,default)
S3method(get_metadata,tdata)
S3method(get_raw_data,TealDataAbstract)
S3method(get_raw_data,TealDataset)
S3method(get_raw_data,TealDatasetConnector)
Expand Down Expand Up @@ -81,13 +86,16 @@ export(fun_dataset_connector)
export(get_attrs)
export(get_cdisc_keys)
export(get_code)
export(get_code_tdata)
export(get_dataname)
export(get_dataset)
export(get_dataset_label)
export(get_datasets)
export(get_join_keys)
export(get_key_duplicates)
export(get_keys)
export(get_labels)
export(get_metadata)
export(get_raw_data)
export(is_pulled)
export(join_key)
Expand All @@ -98,6 +106,7 @@ export(mae_dataset)
export(mutate_data)
export(mutate_dataset)
export(mutate_join_keys)
export(new_tdata)
export(python_cdisc_dataset_connector)
export(python_code)
export(python_dataset_connector)
Expand All @@ -111,6 +120,7 @@ export(script_cdisc_dataset_connector)
export(script_dataset_connector)
export(set_args)
export(set_keys)
export(tdata2env)
export(teal_data)
export(teal_data_file)
export(to_relational_data)
Expand Down
22 changes: 22 additions & 0 deletions R/TealData.R
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,28 @@ TealData <- R6::R6Class( # nolint
private$join_keys$get_parents()
},

#' @description
#' returns the `tdata` object.
#'
#' @return (`tdata`) object of the datasets.
get_tdata = function() {
asbates marked this conversation as resolved.
Show resolved Hide resolved
datanames <- self$get_datanames()
df_list <- lapply(datanames, function(x) self$get_dataset(x)$get_raw_data())
names(df_list) <- datanames
md_list <- lapply(datanames, function(x) self$get_dataset(x)$get_metadata())
names(md_list) <- datanames
labels_list <- lapply(datanames, function(x) self$get_dataset(x)$get_dataset_label())
names(md_list) <- datanames

teal.data::new_tdata(
data = df_list,
join_keys = self$get_join_keys(),
code = self$get_code_class(),
metadata = md_list,
check = self$get_check(),
label = labels_list
)
},
# ___ shiny ====

#' @description
Expand Down
188 changes: 188 additions & 0 deletions R/tdata.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#' Create a `tdata` Object
#'
#' Create a new object called `tdata` which contains `data`, a `reactive` list of data.frames
#' (or `MultiAssayExperiment`), with attributes:
#' \itemize{
#' \item{`code` (`reactive`) containing code used to generate the data}
#' \item{join_keys (`JoinKeys`) containing the relationships between the data}
#' \item{metadata (`named list`) containing any metadata associated with the data frames}
#' }
#' @name tdata
#' @param data A `named list` of `data.frames` (or `MultiAssayExperiment`)
#' which optionally can be `reactive`.
#' Inside this object all of these items will be made `reactive`.
#' @param code A `character` (or `reactive` which evaluates to a `character`) containing
#' the code used to generate the data. This should be `reactive` if the code is changing
#' during a reactive context (e.g. if filtering changes the code). Inside this
#' object `code` will be made reactive
#' @param join_keys A `teal.data::JoinKeys` object containing relationships between the
#' datasets.
#' @param metadata A `named list` each element contains a list of metadata about the named data.frame
#' Each element of these list should be atomic and length one.
#' @param check (`logical`) value whether reproducibility check is requested or not.
#' @param label named (`list`) of datasets labels.
#'
#' @return A `tdata` object
#' @examples
#'
#' data <- new_tdata(
#' data = list(iris = iris, mtcars = reactive(mtcars), ds1 = data.frame(x = 1:10)),
#' code = "iris <- iris
#' mtcars <- mtcars
#' dd <- data.frame(x = 1:10)",
#' metadata = list(ds1 = list(author = "NEST"), iris = list(version = 1)),
#' check = TRUE,
#' label = list(iris = "iris", mtcars = "mtcars", ds1 = "ds1")
#' )
#'
#' # Extract a data.frame
#' isolate(data[["iris"]]())
#'
#' # Get code
#' isolate(get_code_tdata(data))
#'
#' # Get metadata
#' get_metadata(data, "iris")
#'
#' @export
new_tdata <- function(data, code = "", join_keys = NULL, metadata = NULL, check = FALSE, label) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

the check argument here is not in its perfect place and is only passed downstream inside FilteredData to be called in teal::get_datasets_code to evaluate if an warning message should be returned in the SRC or not.
An alternative to this would be not to pass check at all but rather to evaluate if check is TRUE or FALSE upstream and throw a warning message in the console. This will not return a warnings message in the SRC as it is done now but will avoid us the passing an unnecessary argument all through the teal app setup.
@nikolas-burkoff @donyunardi @lcd2yyz

Copy link
Contributor

Choose a reason for hiding this comment

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

#97

checkmate::assert_list(
data,
any.missing = FALSE, names = "unique",
types = c("data.frame", "reactive", "MultiAssayExperiment")
)
checkmate::assert_class(join_keys, "JoinKeys", null.ok = TRUE)
checkmate::assert_multi_class(code, c("character", "reactive", "CodeClass"))

checkmate::assert_list(metadata, names = "unique", null.ok = TRUE)
checkmate::assert_subset(names(metadata), names(data))
for (m in metadata) teal.data::validate_metadata(m)

if (is.reactive(code)) {
isolate(checkmate::assert_multi_class(code(), c("character", "CodeClass"), .var.name = "code"))
}

# create reactive data.frames
for (x in names(data)) {
if (!is.reactive(data[[x]])) {
data[[x]] <- do.call(reactive, list(as.name(x)), envir = list2env(data[x]))
} else {
isolate(
checkmate::assert_multi_class(
data[[x]](), c("data.frame", "MultiAssayExperiment"),
.var.name = "data"
)
)
}
}

# code #nolint
# code <- if (is.reactive(code) && is.character(code())) { #nolint
# CodeClass$new(code()) #nolint
# } else if (is.character(code)) { #nolint
# CodeClass$new(code) #nolint
# } #nolint

# set attributes
attr(data, "code") <- if (is.reactive(code)) code else reactive(code)
attr(data, "join_keys") <- join_keys
attr(data, "metadata") <- metadata
attr(data, "check") <- check
attr(data, "label") <- label

# set class
class(data) <- c("tdata", class(data))
data
}

#' Function to convert a `tdata` object to an `environment`
#' Any `reactives` inside `tdata` are first evaluated
#' @param data a `tdata` object
#' @return an `environment`
#' @examples
#'
#' data <- new_tdata(
#' data = list(iris = iris, mtcars = reactive(mtcars)),
#' code = "iris <- iris
#' mtcars = mtcars",
#' label = list(iris = "iris", mtcars = "mtcars")
#' )
#'
#' my_env <- isolate(tdata2env(data))
#'
#' @export
tdata2env <- function(data) { # nolint
checkmate::assert_class(data, "tdata")
list2env(lapply(data, function(x) if (is.reactive(x)) x() else x))
}

#' @rdname tdata
#' @param x a `tdata` object
#' @param ... additional arguments for the generic
#' @export
get_code.tdata <- function(x, ...) { # nolint
Copy link
Contributor

Choose a reason for hiding this comment

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

To me the home for this is R/get_code.R

# note teal.data which teal depends on defines the get_code method
attr(x, "code")()
}


#' Wrapper for `get_code.tdata`
#' This wrapper is to be used by downstream packages to extract the code of a `tdata` object
#'
#' @param data (`tdata`) object
#'
#' @return (`character`) code used in the `tdata` object.
#' @export
get_code_tdata <- function(data) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand why there are 2 functions here. I could maybe see it if get_code.tdata was not exported, but it is. As a user I would be confused as to which one I should use.

Copy link
Contributor

Choose a reason for hiding this comment

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

So (at least when tdata lived in teal) there was a name clash between get_code() in teal.data and get_code in teal.code and this was a way to force the modules to pull the get_code from teal.data -> if there's a nicer way to do this (without making teal.code and teal.data have a dependency) then happy to change

Copy link
Contributor

Choose a reason for hiding this comment

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

What about axing get_code.tdata and just use get_code_tdata then? It feels like an unnecessary complexity to have both.

If we keep get_code.tdata though, the generic documentation should be updated. Right now the generic reads @param x ([TealDatasetConnector] or [TealDataset]). If of class character will be treated as file to read. It should also say tdata in this case.

Copy link
Contributor

Choose a reason for hiding this comment

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

What about axing get_code.tdata and just use get_code_tdata then? It feels like an unnecessary complexity to have both.

Fair enough

checkmate::assert_class(data, "tdata")
get_code.tdata(data)
}


#' Function to get join keys from a `tdata` object
#' @param data `tdata` - object to extract the join keys
#' @return Either `JoinKeys` object or `NULL` if no join keys
#' @export
get_join_keys <- function(data) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Using S3 here and for metadata feels like overkill.

UseMethod("get_join_keys", data)
}


#' @rdname get_join_keys
#' @export
get_join_keys.tdata <- function(data) {
attr(data, "join_keys")
}


#' @rdname get_join_keys
#' @export
get_join_keys.default <- function(data) {
stop("get_join_keys function not implemented for this object")
}

#' Function to get metadata from a `tdata` object
#' @param data `tdata` - object to extract the data from
#' @param dataname `character(1)` the dataset name whose metadata is requested
#' @return Either list of metadata or NULL if no metadata
#' @export
get_metadata <- function(data, dataname) {
checkmate::assert_string(dataname)
UseMethod("get_metadata", data)
}

#' @rdname get_metadata
#' @export
get_metadata.tdata <- function(data, dataname) {
metadata <- attr(data, "metadata")
if (is.null(metadata)) {
return(NULL)
}
metadata[[dataname]]
}

#' @rdname get_metadata
#' @export
get_metadata.default <- function(data, dataname) {
stop("get_metadata function not implemented for this object")
}
7 changes: 7 additions & 0 deletions _pkgdown.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,10 @@ reference:
- TealDataConnector
- TealDataset
- TealDatasetConnector
- title: Functions for module developers
contents:
- tdata
- get_code_tdata
- get_join_keys
- get_metadata
- tdata2env
14 changes: 14 additions & 0 deletions man/TealData.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions man/get_code_tdata.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions man/get_join_keys.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 25 additions & 0 deletions man/get_metadata.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading