-
Notifications
You must be signed in to change notification settings - Fork 3
Extend normal outcome functions to two-sided decisions #28
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
base: develop
Are you sure you want to change the base?
Conversation
weberse2
left a comment
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.
Overall this looks like a good direction. Thanks. There are a number of comments I had during review.
Two general remarks:
- the code must be contributed under the GPLv3 and will stay under the GPLv3
- Please use checkmate for most stuff... in particular if multiple
conditions are checked on a single object. Checkmate produces far
better error messages by default from my experience. No need to revise
the entire package for this, but whenever you introduce some assert,
consider the checkmate variant to check most stuff at once.
Also, please consider to revise the following pattern:
lower.tail <- attr(decision, "lower.tail")
if (length(lower.tail) > 1) {
use_lower <- which(lower.tail)
use_upper <- which(!lower.tail)
assert_true(length(use_lower) > 0 && length(use_upper) > 0)
dec_lower <- decision[use_lower]
crit_lower <- decision1S_boundary.normMix(
prior,
n,
dec_lower,
sigma,
eps
)
dec_upper <- decision[use_upper]
crit_upper <- decision1S_boundary.normMix(
prior,
n,
dec_upper,
sigma,
eps
)with something where we put this logic into the decision function
itself? So when setting up the decision function we check if it is
two-sided. Then we make that an attribute of the decision function so
that it knows that is is one/two sided. The decision object itself
could then also have a upper and lower decision object as an attribute
which are themselves one-sided in each direction.
Then the above would become
num_sides <- attr(decision, "sides")
if(num_sides != 1) {
crit_lower <- decision1S_boundary.normMix(
prior,
n,
attr(decision, "lower"),
sigma,
eps
)
crit_upper <- decision1S_boundary.normMix(
prior,
n,
attr(decision, "upper"),
sigma,
eps
)
}
And as the code needs quite often to know if it is two sided or one
sided we should go one further and have an internal function
.is_one_sided <- function(decision) {
return TRUE / FALSE depending on whatever the thing is
}
Then the code gets much easier to follow along.
Finally, I would like to merge this onto a develop branch first. As
this work is not yet complete once this normal case is mixed, I would
not like it on the main branch to be merged. The main branch should
stay clean from half-baked features. Best practice is to have main be
the released thing, which I did not follow in the past, but with
staged feature inclusion, we need this.
Could you also update the NEWS file indicating that this feature is in
progress?
|
Thanks a lot @weberse2 for your thoughtful review. I have implemented the suggested pattern change (with a slight variation, using an S3 class instead of merely an attribute to differentiate two-sided decision functions), as well as addressed the minor comments. I agree (as stated in the PR description already) that the code will be contributed under GPLv3. I recommend to
|
weberse2
left a comment
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 minor stuff now. I am a bit worried on snapshot tests, see the comment below.
I would also appreciate to make the doc for users sound less technical - whatever class something has is possibly confusing to many users.
The class for a 2sided case is neat. I was wondering if one could even assign decision1S_1/2sided, decision1S as classes.. but maybe that's overkill and we can change it to this approach later easily?
I also noticed that the SBC tests have aged too much so that the gold runs are now too old. I can handle that.
Ah.. what would be nice for users is to have a small example of the new functionality in the examples. Some examples are already long... still a brief one would be useful, I think.
Getting close to merge.
R/decision1S.R
Outdated
| if (is_two_sided) { | ||
| create_decision1S_2sided(pc, qc, lower.tail) | ||
| } else { | ||
| create_decision1S_1sided(pc, qc, lower.tail) |
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.
Can u please add return here in each case explicitly?
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.
Sure, done (just fyi https://style.tidyverse.org/functions.html#return discourages explicit returns when they are not needed)
| attr(fun, "pc") <- pc | ||
| attr(fun, "qc") <- qc | ||
| attr(fun, "lower.tail") <- lower.tail | ||
|
|
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.
since there is no lower/upper attribute, one would get null when requesting these. Wouldn't it make more sense to have lower/upper attribute being defined for whatever the 1S is for... the other one should be set to NULL, which probably does not need defintion.
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 understand, that would also be possible. So far I was trying to be fully backwards compatible with this enhancement, i.e. I did not want to change the current one-sided objects or structure. Therefore I did not try to add attributes here or change the overall class structure for one-sided. If you think that is not important, then I can try to make the one-sided and two-sided more consistent with each other (i.e. closer to the more general two-sided)
| 134.005773713552, 137.005622352737, 140.005470991921, 143.005319631105, | ||
| 146.00516827029, 149.005014890798, 152.004861511307, 155.004708131815, | ||
| 158.004554752324, 161.004401372832, 164.004247993341, 167.004094613849, | ||
| 170.003941234358, 173.003787854866, 176.003634475375) |
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 not used snapshot tests myself. These were not a thing when I worked on this. My worry when I see this here, is that the enormous precision here is super fragile over time. Maybe I need to read a bit how this test works in this case? Could you point me where I need to read up to understand this. As long as the test is robust and not prone to irrelevant floating point business, I am fine with it.
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.
The general intro is here: https://testthat.r-lib.org/articles/snapshotting.html
Specifically I am using expect_snapshot_value, see https://testthat.r-lib.org/reference/expect_snapshot_value.html, which tolerates relative numerical differences up to sqrt(.Machine$double.eps) (we could also add a little helper which has a larger tolerance if we run into issues) Also, on CRAN checks these snapshot tests are skipped.
R/decision1S.R
Outdated
| #' are \eqn{P(X \leq x)}, otherwise, \eqn{P(X > x)}. | ||
| #' are \eqn{P(X \leq x)}, otherwise, \eqn{P(X > x)}. Either length 1 or same | ||
| #' length as `pc`. | ||
| #' @param x object of class `decision1S_2sided`. |
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 that users care about the class. So this should read just two-sided decision function... unless we switch to the logic I suggested to have this lower/upper being defined for one-sided functions as well, which I would actually prefer? Or is there a good reason not to have it consistent?
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.
changed - please see my other comment re: consistency vs. backwards compatibility question.
|
Thanks @weberse2!
|
normal outcome part of #27
Summary of the updates:
decision1Sanddecision2S: allowslower.tailto have as many elements aspcto allow the specification of two-sided decision boundaries (mixedlower.tailelements, i.e. some specifying "lower" and some "upper") to capture intermediate result scenarios.decision1S_boundary.normMixanddecision2S_boundary.normMix: for two-sided decision boundaries, return a list oflower_thanandhigher_thancritical boundaries (calculated as before, and separately).oc1S.normMix: for two-sided decision boundaries, calculates the probability to be in-between thelower_thanandhigher_thanbounds, as returned bydecision1S_boundary.normMixoc2S.normMix: for two-sided decision boundaries, defines the internalfreqfunction accordingly to use thelower_thanandhigher_thanbounds as produced from the giventheta1andtheta2values.pos1S.normMix: similar update here as inoc1S.normMixpos2S.normMixsimilar update tooc2S.normMixNotes: