-
-
Notifications
You must be signed in to change notification settings - Fork 6
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
Simplify package #498
Simplify package #498
Conversation
@@ -575,15 +562,17 @@ FilterState <- R6::R6Class( # nolint | |||
# @description | |||
# Return variable name prefixed by `dataname` to be evaluated as extracted object, | |||
# for example `data$var` | |||
# @param dataname `character(1)` name of a dataset | |||
# @param extract_type `character(1)` type of extraction, one of `character(0)`, `"list"`, `"matrix"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I kind of dislike mixing
character(0)
withcharacter(1)
. Usually there is a support of "none" value instead ofcharacter(0)
. - If you do the above you can then very easily validate values with
match.arg()
. Current implementation is rather:"list"
,"matrix"
and everything else. (don't know if this is checked elsewhere - I'm looking at this method only)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know exactly how to call each way of extraction:
character(0)
means that variables in asubset
call are not prefixed (data |> subset(x == "a")
)list
means that variables are extracted by$
in a subset call (data |> subsetByColData(data$x == "a")
)matrix
means that variables are extracted by[[
in a subset call (data |> subset(data[, "x"] == "a")
)
Somebody has better terms for above cases?
# @param extract_type `character(1)` type of extraction, one of `character(0)`, `"list"`, `"matrix"` | |
# @param extract_type `character(1)` type of extraction, one of `"nse"`, `"list"`, `"matrix"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps change the name of the argument to var_prefix
or something like that and then c("none", "list", "matrix")
will be proper options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps change the name of the argument to
var_prefix
or something like that and thanc("none", "list", "matrix")
will be proper options?
Yes exactly.
I have one more thought but I'm not yet super convinced to this. I'm interested in hearing your thoughts on this.
Instead of a string which is then matched to sprintf pattern, we can allow providing sprintf pattern or the whole sprintf call.
Taking this idea further - we are actually talking about an expression and not message / warning / anything else that requires a character type of data. Hance, instead of a sptrintf that produced a char which is then parsed to expression -> move to substitute that returns expression directly without string based step. I mean - operate on the right data type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Note this is an internal class and the user never provides anything, unless you count senior classes as users.
- We have gone through operating on strings and language objects throughout
teal.slice
back in Spring. Trust me, the code to build this as a string is much, much simpler than that required to build expression. This is currently (I believe) the only departure from building expressions from language objects in the package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have one more thought but I'm not yet super convinced to this. I'm interested in hearing your thoughts on this.
Instead of a string which is then matched to sprintf pattern, we can allow providing sprintf pattern or the whole sprintf call.
Theoretically you can just provide get_call(varname = "dataname$varname")
, but this doesn't make sense, because dataname
and varname
is an information kept inside of FilterState
class. When gathering calls from all FilterState
object, parent (method which gathers) doesn't know anything about varname
.
Providing sprintf pattern is not enough neither, because:
sprintf("%s$%s", dataname, varname)
sprintf('%s[, "%s"]', dataname, varname)
- `sprintf("%s", varname)
As you can see, dataname in first two is a first argument and varname second. In case of (3) dataname is not an argument for sprintf at all and varname is second.
Pattern is hard do handle because different arguments are taken in all cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so this thread doesn't get buried and forgotten, I suggest to change extract_type
to
var_prefix = c("none", "list", "matrix")
which could then be handled with
var_prefix <- match.arg(var_prefix)
and
ans <- switch(var_prefix, ...)
@@ -9,7 +9,7 @@ | |||
#' library(shiny) | |||
#' datasets <- teal.slice::init_filtered_data(list(iris = iris, mtcars = mtcars)) | |||
#' @export | |||
init_filtered_data <- function(x, join_keys = teal.data::join_keys(), code, check) { # nolint | |||
init_filtered_data <- function(x = list(), join_keys = teal.data::join_keys(), code, check) { # nolint |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- I don't think
x
should have a default value. Usually a default value is a signal that a given argument is not obligatory which is not the case. - If we are talking about this line - there are
code
andcheck
argumnets that are both deprecated and without a default value. I think we can safely add default values. Note thatmissing()
used below only checks if a value was explicitely specified in the function call.
foo <- \(x = NULL) {`if`(missing(x), 0, 1)}
foo()
#> [1] 0
foo(10)
#> [1] 1
Created on 2023-12-20 with reprex v2.0.2
Can you please provide some more explanation behind new dispatching? I can't stop having a feeling that it's a step back from what we are having currently. You raised a |
This is debatable and under investigation.
Rather than keeping R6 classes for every supported class of dataset, we have
I think what @gogonzo was not talking about a dataset being a
MAE is composed of experiments and therefore is a nested structure. Experiments can be By creating standalone methods (method suites, actually) for any class that we expect to be an experiment in MAE we allow them to be standalone datasets themselves. The methods for MAE simply dispatch to methods relevant for particular experiment classes. |
Thanks @chlebowa for the context. I agree with you that extending should be done with a new S3 method which is much easier than a new R6 class. I was more referring to the big if statement (that is conceptually more a switch than if) in
I think we are in agreement here - this should be a new method. Please just note that data.table way of filtering works only if a filtered object is a data.table. It does not work for a data.frame - the function used is |
Yup, exactly.
One can wish to handle (*) Since |
Currently (on main) SummarizedExperiment, Matrix are handled only as a subitem of MAE. In this PR it doesn't matter if the this is a part of a larger-nested structure or not. It opens possibility to apply filter-panel for objects with any nested structure.
I've checked that
My mistake. I don't know why i wrote |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love the idea but I think it needs a lot of refinement.
get_filter_call
requires exposing the FilterState
class, and the FilteredData$state_list
member, both of which were hitherto private, and for good reason.
Extending support to a new class would require deep knowledge of the entire FilterPanel
, probably more than I have myself. I find a third party writing their own srv_add
and srv_active
functions unrealistic.
Some specific suggestions for get_filter_call
:
- should take predicates, not
FilterState
s - should use hard-coded subsetting function to build subsetting expression
- could take
dataname
as argument - perhaps move varname preixing from
FilterState$get_call
to this function?
--keep_na
andkeep_inf
could be tough to handle
-- perhaps use something akin totmc::substitute_names
? - final stages of filter call construction are esoteric
rhs <- as.call(c(str2lang("dplyr::filter"), c(list(dataname_lang), combined_predicate)))
substitute(
expr = dataname_lang <- rhs,
env = list(dataname_lang = dataname_lang, rhs = rhs)
)
-- they will be looked at by people implementing their own methods
-- perhaps introduce a helper function like get_final_call <- function(dataname, predicates(prefixed), FUN)
We can still accept this as a good direction of change even if all the mechanisms remain internal and the Core Dev team takes on responsibility for adding support for all requested classes.
@@ -22,9 +22,9 @@ | |||
#' @keywords internal | |||
calls_combine_by <- function(calls, operator) { | |||
checkmate::assert_list(calls) | |||
if (length(calls) > 0L) checkmate::assert_list(calls, types = c("call", "name")) | |||
checkmate::assert_string(operator) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we could limit operator
to valid operators &
and |
(possibly xor
)?
@@ -575,15 +562,17 @@ FilterState <- R6::R6Class( # nolint | |||
# @description | |||
# Return variable name prefixed by `dataname` to be evaluated as extracted object, | |||
# for example `data$var` | |||
# @param dataname `character(1)` name of a dataset | |||
# @param extract_type `character(1)` type of extraction, one of `character(0)`, `"list"`, `"matrix"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just so this thread doesn't get buried and forgotten, I suggest to change extract_type
to
var_prefix = c("none", "list", "matrix")
which could then be handled with
var_prefix <- match.arg(var_prefix)
and
ans <- switch(var_prefix, ...)
@@ -124,8 +124,10 @@ FilterStateExpr <- R6::R6Class( # nolint | |||
#' for selected variable type. | |||
#' Method is using internal reactive values which makes it reactive | |||
#' and must be executed in reactive or isolated context. | |||
#' @param extract_type (`character(0)`, `character(1)`)\cr | |||
#' ignored |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' ignored | |
#' for compatibility with `FilterState$get_call`; ignored |
#' @param id (`character`) Module id | ||
#' @param data (`object`) Object of any class | ||
#' @param data_filtered (`object`) Object of any class but must be the same class as `data` | ||
#' @param dataname (`character`) Name of the dataset | ||
#' @param filtered_data (`FilteredData`) Object of class `FilteredData`. | ||
#' @param states_list (`list`) List of `FilterState` objects |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#' @param id (`character`) Module id | |
#' @param data (`object`) Object of any class | |
#' @param data_filtered (`object`) Object of any class but must be the same class as `data` | |
#' @param dataname (`character`) Name of the dataset | |
#' @param filtered_data (`FilteredData`) Object of class `FilteredData`. | |
#' @param states_list (`list`) List of `FilterState` objects | |
#' @param id (`character`) `shiny` module id. | |
#' @param data Data object of any class. | |
#' @param data_filtered Subset of `data`. | |
#' @param dataname (`character`) Name of the dataset. | |
#' @param filtered_data (`FilteredData`) Object of class `FilteredData`. | |
#' @param states_list (`list`) List of `FilterState` objects. |
#' | ||
#' `teal.slice` provides set of methods to manage filters for different data types. | ||
#' # todo: write more about [srv_add()] and consequences of creating `teal_slice` for certain dataset | ||
#' # - how slice defines location of the variable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is covered in ?teal_slice
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation says one of the supported class is "matrix"
but the functions use "array"
. This latter should be changed as we support matrices but not (n-dimensional) arrays.
I think we should provide methods for DataFrame
because they (DataFrame
s) are found in SE
and MAE
slots and they do not inherit from data.frame
.
R/FilteredData.R
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FilteredData
$format
and $print
methods were lost.
Just a reminder not to merge this before our teal release. Pushing this PR to later sprint. |
@donyunardi is this the proper sprint for this card? |
Maybe not as we still have other priorities. |
WIP
fixes
teal
)This is a proposition to simplify
FilteredData
as much as possible (for me today). Some information are written in vignette for developers and inR/methods-documentation.R
file.Changes:
FilteredDataset
andFilterStates
FIlteredData
is the class to keep and manage all statesSummarizedExperiment
can be standalone dataset and could be nested insideMultiAssayExperiment
object. Same could apply tolists
, "spatial" objects and other data classes composed of other smalled structures. This can support any nesting deptdplyr::filter
call withdata.frame
. Theoretically, we should export onlyxyz.default
method so everyone can override methods for specific classes (for example data.frame). If we register S3 method fordata.frame
it will be not possible to overridedata.frame
method. Confirmed here Change dispatch method to S3 instead of custom (target:@132_filter_panel_ui@main
) #501testing app