-
Notifications
You must be signed in to change notification settings - Fork 26
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
Fixes issue #36 #37
Fixes issue #36 #37
Conversation
Codecov Report
@@ Coverage Diff @@
## master #37 +/- ##
========================================
+ Coverage 98% 98.3% +0.3%
========================================
Files 5 5
Lines 100 118 +18
========================================
+ Hits 98 116 +18
Misses 2 2
Continue to review full report at Codecov.
|
OOPs, sorry, wrong button, reopenning |
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.
Hi! I'm sorry that it has taken me so long to respond. I was away on vacation, and I just got back to my computer.
Thanks for your contributions. I really appreciate all of the effort that you put into this PR. I left several comments on the code in your PR. One was about making cmat
more robust to abnormal inputs. Most of them were about making improvements to the documentation. I care a lot about the quality of documentation in this package, which is why I put a lot of time into reviewing it.
Also, in this PR, you didn't commit any of the documentation files. Can you run devtools::document()
and commit the .Rd
files that get generated?
Let me know if you are interested in making the changes that I requested on this PR and if you have any questions about the requests that I made.
Lastly, please add yourself as a contributor to the Authors section in the DESCRIPTION file!
Addresses issue #36 |
Hi @mfrasco, will take a look at all your suggestions for improvement, and will fix them, hopefully sooner rather than later. Fortunately here (in Peru) will be a shorter week than usual, for our National holidays so I'll have time to fix this. In particular the bit about making the confusion matrix robust (it is very naive now). |
if (!( | ||
setequal(binvals, b_actual) | | ||
setequal(c(0), b_actual) | | ||
setequal(c(1), b_actual) |
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.
You don't need b_actual
nor the three calls to setequal()
, but instead !all(actual %in% 0:1)
should do.
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.
Yes. I agree that !all(actual %in% 0:1)
is a good implementation.
This is a breaking change to the existing functionality of the package, as the current implementation does not break if the user provides a vector that doesn't contain 0 and 1.
When I took over maintenance of this package, I thought a lot about whether I should validate the inputs. At the time, it wasn't worth the effort, but I understand why we would want to implement it now.
If we do add input validation, I'd want this logic to be pulled into a separate function and used consistently across all binary classification metrics.
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.
Wanted to write so you know I am still interested in helping here. Sorry for not been responsive for quite some time, got a bit of health issues that are resolving, and have not had time to look at the code and suggestions. Hopefully by the end of this week.
A quick glance seems like @mllg suggestions are on point. As for input validation, it will be a "good thing" (TM), but needs some thought I think, in particular if it involves breaking bc.
Perhaps the idea of putting this into a side branch will be better, so things can be merged to the main branch once they are in their final form.
if (!( | ||
setequal(binvals, b_predicted) | | ||
setequal(c(0), b_predicted) | | ||
setequal(c(1), b_predicted) |
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.
See above, same for b_predicted
.
"fn" = sum(actual == 1 & predicted == 0), | ||
"fp" = sum(actual == 0 & predicted == 1), | ||
"tn" = sum(actual == 0 & predicted == 0) | ||
) |
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.
A data.frame()
seems like an odd choice to return a integer(4)
.
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 agree. A named vector would be simpler.
#' actual <- c(1, 1, 1, 0, 0, 0) | ||
#' predicted <- c(1, 1, 1, 1, 1, 1) | ||
#' precision(actual, predicted) | ||
precision <- function(actual, predicted) { | ||
return(mean(actual[predicted == 1])) | ||
cm <- confusion_matrix(actual, predicted) | ||
cm$tp / (cm$tp + cm$fp) |
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 returns NaN
if tp
and fp
both are 0. This is absolutely fine, but should be clearly documented though. Also note that other toolkits decided to return either 0 or 1 in such a case. There is a discussion about it on SO: https://stats.stackexchange.com/questions/1773/what-are-correct-values-for-precision-and-recall-in-edge-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.
The existing implementation of the function returns NaN
, so we should be intentional about making this change. It'd need to be consistent across the entire package.
I'd support returning 0 when cm$tp + cm$fp == 0
and raising a warning that says something like Precision is called with no positive predictions. The proper definition of precision is undefined. Returning 0 instead.
Sorry for hijacking this PR, just stumbled over it as I thought about creating a feature request for more binary classification measures. FWIW, I implemented something similar for
|
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 really appreciate your effort into this PR. And I'm sorry that it's taken me a while to respond. The changes proposed in this PR are significant because they change the behavior of existing functions, which means that we need to be thoughtful with what users expect from this package. For that reason, I have more feedback on the types of changes that I'd like to make on this PR.
I also recognize that this PR is becoming quite large in scope. For that reason, we might take a first step of implementing the basic logic in all of the functions but not exporting them in the package. Or we might take the strategy of merging this PR into a non-master branch within this repo. So that further development can be made. Does that make sense?
Let me know what you want to do.
#' actual <- c(1, 1, 1, 0, 0, 0) | ||
#' predicted <- c(1, 1, 1, 1, 1, 1) | ||
#' precision(actual, predicted) | ||
precision <- function(actual, predicted) { | ||
return(mean(actual[predicted == 1])) | ||
cm <- confusion_matrix(actual, predicted) | ||
cm$tp / (cm$tp + cm$fp) |
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 existing implementation of the function returns NaN
, so we should be intentional about making this change. It'd need to be consistent across the entire package.
I'd support returning 0 when cm$tp + cm$fp == 0
and raising a warning that says something like Precision is called with no positive predictions. The proper definition of precision is undefined. Returning 0 instead.
if (!( | ||
setequal(binvals, b_actual) | | ||
setequal(c(0), b_actual) | | ||
setequal(c(1), b_actual) |
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.
Yes. I agree that !all(actual %in% 0:1)
is a good implementation.
This is a breaking change to the existing functionality of the package, as the current implementation does not break if the user provides a vector that doesn't contain 0 and 1.
When I took over maintenance of this package, I thought a lot about whether I should validate the inputs. At the time, it wasn't worth the effort, but I understand why we would want to implement it now.
If we do add input validation, I'd want this logic to be pulled into a separate function and used consistently across all binary classification metrics.
setequal(c(1), b_actual) | ||
)) { | ||
stop(paste("Expecting a vector of 0s and 1s for 'actual'. Got:", | ||
paste(actual, collapse = ", "))) |
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.
If actual
has many values, this print statement could be very large. You'd probably want to only show the first 5 or so unique values.
"fn" = sum(actual == 1 & predicted == 0), | ||
"fp" = sum(actual == 0 & predicted == 1), | ||
"tn" = sum(actual == 0 & predicted == 0) | ||
) |
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 agree. A named vector would be simpler.
#' predicted <- c(1, 0, 1, 1, 1, 1) | ||
#' sensitivity(actual, predicted) | ||
sensitivity <- function(actual, predicted) { | ||
recall(actual, predicted) |
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.
Thanks for implementing sensitivity as a function of recall.
I'm thinking of a better way to implement this so that sensitivity and recall actually are defined as the same function, not just one function that calls another. I want the functions to share documentation too. I'll look into the best ways for doing this.
I'm thinking about
foo <- bar <- baz <- function(actual, predicted {
...
}
But I need to do research into how that impacts R packages and documentation.
#' predicted <- c(1, 0, 1, 1, 1, 1) | ||
#' fnr(actual, predicted) | ||
fnr <- function(actual, predicted) { | ||
1 - sensitivity(actual, predicted) |
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 good. I need to decide if this should be 0
or 1
in the case where sum(actual) == 0
@mllg Thanks for your feedback on the PR. I really appreciate it and I thought all of your ideas were terrific. I took a look at mlr3. I like how it defined the measures that have multiple names in one location. That's something that I want to accomplish with this PR too. How do you think that the Metrics package should think about backwards compatibility issues (e.g. returning 0 instead of NaN in precision when |
Moved the proposed code changes to https://github.com/jmcastagnetto/Metrics/tree/fix36, haven't reviewed them since last august. Closing this until I get time to make the suggested improvements or something else is decided. |
Adds sensitivity, specificity and other related metrics. To make it simpler, added a simple binary confusion matrix function that is not exported.