Multipop calcPopValue and updates#287
Conversation
…tput were different
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## MultPop #287 +/- ##
==========================================
Coverage ? 46.82%
==========================================
Files ? 38
Lines ? 10067
Branches ? 0
==========================================
Hits ? 4714
Misses ? 5353
Partials ? 0 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| #' ) | ||
| #' ) | ||
| #' | ||
| #' # Calculate phenotypes at different levels |
There was a problem hiding this comment.
calcPopValue() is becoming a widely used "workhorse" function for much of work with "values" in MultiPop objects. This is great. We initially intended it as a "calculate some summary for each population object in a MultiPop object", so calcPopValue() was an obvious choice name. But now it also used widely to "get just about any value fro each population object in a MultiPop object", making me wonder if it would be better to call this getPopValue() and then what this getter returns depends on its FUN argument.
I am raising this because in our recent meeting I saw calcPopValue() being used over and over to get individual values for each population of a MultiPop object and the term calc was confusing me all the time.
This example in the next line calcPopValue(mp1, FUN = pheno, ...) is a nice example of this - here we are just extracting phenotypes, while with calcPopValue(mp1, FUN = function(x) colMeans(pheno(x)) ...) we are calculating.
This is all just semantics! In most cases users will call functions that internally call this calcPopValue/getPopValue so naming is not critical, but could help us understand what we are doing.
There was a problem hiding this comment.
Maybe I am overthinking this!?
|
About
I think I like your choice of default ( @gaynorr what are your thoughts? |
I really like how |
I like the chosen defaults Arguably @gaynorr what are your thoughts on this? |
I reviewed this PR with some time-distance and jumped straight into the code, so interesting that I commented up on the blur between the |
gregorgorjanc
left a comment
There was a problem hiding this comment.
Minor bits left to iron out, but this is close to getting merged. Would be good to get your feedback to @gaynorr!
| } | ||
|
|
||
| #' Calculate summary response from a population | ||
| #' @title Calculate values from Pop or MultiPop objects |
There was a problem hiding this comment.
Revise the doc in relation to the calcX() vs getX() discussion point below.
This PR adds new functionality to the
MultiPopclass and updatesflattenMultiPopandcalcPopValue.Summary of changes
Add
splitPop, which makes it easier to create flat or nestedMultiPopobjects from grouping specifications. Grouping specs are passed through thebyargument as atomic vectors or functions returning such vectors. Ifbyis a list, each element is applied recursively to build nestedMultiPopobjects.Update
flattenMultiPopto better preserve names in nested structures. A new argument,preserveNames, controls how names are handled when flattening:"auto"(default): keep names only if they are all present and remain unique after flattening; otherwise drop them."concatenate": prefix child names with parent names using underscores. Names are kept only if the resulting values are unique."force": always keep concatenated names and make them unique withmake.unique()if needed, with a warning."none": drop all names.The default
"auto"mode is conservative: names are preserved only when they remain meaningful. Use"concatenate"to build hierarchical names when possible,"force"to guarantee unique names, and"none"to explicitly discard names.Update
calcPopValueto a more general form controlled by a user-providedFUNfunction.calcPopValueis now exported and also gains asimplifyargument, similar tosapply()and related functions. This allows the output to be simplified to a requested nestinglevel, and the simplification is handled byflattenMultiPop.To avoid losing track of the original structure that produced the simplified output, each resulting matrix now carries a
"source"attribute. This is a data frame with one column per nesting level in the original object and one row per row in the simplified output matrix. Each entry records the name (character) or index (integer if no name available) of the parent node at that level.Update
selectPopto use the new version ofcalcPopValue.Commit overview
19296fd
Adds the exported
splitPopfunction and the internal.splitAtLevelsand.splitPophelpers, along with documentation and tests.10583df
Updates
flattenMultiPopto include thepreserveNamesargument. Documentation and tests were updated as well.af2a49e
Updates
calcPopValueto support asimplifyargument. To build the new"source"attribute, this commit adds the internal helpers.collectLeafPathsand.formatCalcPopSource. Documentation and tests were also updated.0827426
Updates
selectPopto use the new version ofcalcPopValue. Documentation and tests were also updated.9c298a3
Fixes a bug where the
"source"attribute and the simplified matrix had different numbers of rows.53b14dc
Changes automatically introduced by GitHub actions after pushing to origin.
Discussion topics
Please let me know your thoughts about:
splitPopbuilds nestedMultiPopobjects from a list of functions.preserveNamesargument inflattenMultiPop, particularly on whether the default behavior ("auto") feels sensible.calcPopValue, its overall design and implementation.simplify=FALSE,level=1)? This implies that, wheneversimplify=TRUE, the input will be flattened tolevel=1(unless the user specifies otherlevel), producing a single matrix for the entire structure. A second option would be to calculate the input's max depth (with.depthMultiPop) which would produce a matrix for each pop arranged in a list with the same structure as the inputx. A third option would be to leavelevelundefined and expect the user to define it wheneversimplify=TRUE, raising an error if they don't.calcPopValuecould become a useful base function for other “get” functions. For example, we could updatepheno(and similarlygvandebv) to use the newcalcPopValuefunctionality, which would let them work withMultiPopobjects:Similarly, a new
phenoPopto calculate pop-level phenotypes can be also implemented to usecalcPopValue:Other functions in the
pullXXXfamily could likely be updated in a similar way, although they would need a more substantial refactor thanpheno:That said, as @gregorgorjanc pointed out, this may blur the line between "getting" values from
Popobjects and "calculating" new values based on the contents of eachPopwithin aMultiPop(see #239 (comment)). It could also feel a bit confusing for users to see acalcfunction used inside functions that are conceptually about retrieving values.Related to #239