-
Notifications
You must be signed in to change notification settings - Fork 13
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
Added percent functions. #130
base: master
Are you sure you want to change the base?
Changes from 6 commits
a52f805
6bd2798
9142f86
7ce3ebe
322f848
f7382fc
6aa3ce0
9a7b634
61e5d79
b9a9151
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
#' Percentages | ||
#' | ||
#' @description | ||
#' | ||
#' `percent` is a lightweight S3 class allowing for pretty | ||
#' printing of proportions as percentages. \cr | ||
#' It aims to remove the need for creating character vectors of percentages. | ||
#' | ||
#' @param x [`numeric`] vector of proportions. | ||
#' | ||
#' @returns | ||
#' A class of object `percent`. | ||
#' | ||
#' @details | ||
#' | ||
#' ### Rounding | ||
#' | ||
#' The rounding for percent vectors differs to that of base R rounding, | ||
#' namely in that halves are rounded up instead of rounded to even. | ||
#' This means that `round(x)` will round the percent vector `x` using | ||
#' halves-up rounding (like in the janitor package). | ||
#' | ||
#' ### Formatting | ||
#' | ||
#' By default all percentages are formatted to 2 decimal places which can be | ||
#' overwritten using `format()` or using `round()` if your required digits are | ||
#' less than 2. It's worth noting that the digits argument in | ||
#' `format.percent` uses decimal rounding instead of the usual | ||
#' significant digit rounding that `format.default()` uses. | ||
#' | ||
#' @examples | ||
#' library(phsmethods) | ||
#' | ||
Moohan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#' # Convert proportions to percentages | ||
#' as_percent(seq(0, 1, 0.1)) | ||
#' | ||
#' # You can use round() as usual | ||
#' p <- as_percent(15.56 / 100) | ||
#' round(p) | ||
#' round(p, digits = 1) | ||
#' | ||
#' p2 <- as_percent(0.0005) | ||
#' signif(p2, 2) | ||
#' floor(p2) | ||
#' ceiling(p2) | ||
#' | ||
#' # We can do basic math operations as usual | ||
#' | ||
#' # Order of operations matters | ||
#' 10 * as_percent(c(0, 0.5, 2)) | ||
#' as_percent(c(0, 0.5, 2)) * 10 | ||
#' | ||
#' as_percent(0.1) + as_percent(0.2) | ||
#' | ||
#' # Formatting options | ||
#' format(as_percent(2.674 / 100), digits = 2, symbol = " (%)") | ||
#' # Prints nicely in data frames (and tibbles) | ||
#' library(dplyr) | ||
#' starwars %>% | ||
#' count(eye_color) %>% | ||
#' mutate(perc = as_percent(n/sum(n))) %>% | ||
#' arrange(desc(perc)) %>% # We can do numeric sorting with percent vectors | ||
#' mutate(perc_rounded = round(perc)) | ||
#' @export | ||
#' @rdname percent | ||
as_percent <- function(x){ | ||
if (!is.numeric(x)){ | ||
stop("x must be a numeric vector of proportions") | ||
Moohan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
new_percent(x) | ||
} | ||
new_percent <- function(x){ | ||
class(x) <- "percent" | ||
x | ||
} | ||
round_half_up <- function(x, digits = 0){ | ||
if (is.null(digits) || (length(digits) == 1 && digits == Inf)){ | ||
return(x) | ||
} | ||
trunc( | ||
abs(x) * 10^digits + 0.5 + | ||
sqrt(.Machine$double.eps) | ||
) / | ||
10^digits * sign(x) | ||
} | ||
signif_half_up <- function(x, digits = 6){ | ||
if (is.null(digits) || (length(digits) == 1 && digits == Inf)){ | ||
return(x) | ||
} | ||
round_half_up(x, digits - ceiling(log10(abs(x)))) | ||
} | ||
|
||
#' @export | ||
as.character.percent <- function(x, digits = 2, ...){ | ||
stringr::str_c(unclass(round(x, digits) * 100), "%") | ||
} | ||
|
||
#' @export | ||
format.percent <- function(x, symbol = "%", trim = TRUE, | ||
Moohan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
digits = 2, | ||
...){ | ||
out <- stringr::str_c( | ||
format(unclass(round(x, digits) * 100), trim = trim, digits = NULL, ...), | ||
symbol | ||
) | ||
out[is.na(x)] <- NA | ||
names(out) <- names(x) | ||
out | ||
} | ||
|
||
#' @export | ||
print.percent <- function(x, max = NULL, trim = TRUE, | ||
digits = 2, | ||
...){ | ||
out <- x | ||
N <- length(out) | ||
if (N == 0){ | ||
print("as_percent(numeric())") | ||
return(invisible(x)) | ||
} | ||
if (is.null(max)) { | ||
max <- getOption("max.print", 9999L) | ||
} | ||
suffix <- character() | ||
max <- min(max, N) | ||
if (max < N) { | ||
out <- out[seq_len(max)] | ||
suffix <- stringr::str_c( | ||
" [ reached 'max' / getOption(\"max.print\") -- omitted", | ||
N - max, "entries ]\n", | ||
sep = " " | ||
) | ||
} | ||
print(format(out, trim = trim, digits = digits), ...) | ||
cat(suffix) | ||
invisible(x) | ||
} | ||
|
||
#' @export | ||
`[.percent` <- function(x, ..., drop = TRUE){ | ||
cl <- oldClass(x) | ||
class(x) <- NULL | ||
out <- NextMethod("[") | ||
class(out) <- cl | ||
out | ||
} | ||
Comment on lines
+137
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this do? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It just ensures that the next appropriate method for subsetting is used (in this case numeric vector subsetting) and adds the appropriate class back. That means There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can maybe get rid of |
||
|
||
#' @export | ||
unique.percent <- function(x, incomparables = FALSE, | ||
fromLast = FALSE, nmax = NA, ...){ | ||
cl <- oldClass(x) | ||
class(x) <- NULL | ||
out <- NextMethod("unique") | ||
class(out) <- cl | ||
out | ||
} | ||
|
||
#' @export | ||
rep.percent <- function(x, ...){ | ||
cl <- oldClass(x) | ||
class(x) <- NULL | ||
out <- NextMethod("rep") | ||
class(out) <- cl | ||
out | ||
} | ||
|
||
#' @export | ||
Math.percent <- function(x, ...){ | ||
rounding_math <- switch(.Generic, | ||
`floor` =, | ||
`ceiling` =, | ||
`trunc` =, | ||
`round` =, | ||
`signif` = TRUE, FALSE) | ||
x <- unclass(x) | ||
if (rounding_math){ | ||
x <- x * 100 | ||
if (.Generic == "round"){ | ||
out <- do.call(round_half_up, list(x, ...)) | ||
} else if (.Generic == "signif"){ | ||
out <- do.call(signif_half_up, list(x, ...)) | ||
} else { | ||
out <- NextMethod(.Generic) | ||
} | ||
new_percent(out / 100) | ||
} else { | ||
out <- NextMethod(.Generic) | ||
new_percent(out) | ||
} | ||
} | ||
Moohan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#' @export | ||
Summary.percent <- function(x, ...){ | ||
summary_math <- switch(.Generic, | ||
`sum` =, | ||
`prod` =, | ||
`min` =, | ||
`max` =, | ||
`range` = TRUE, FALSE) | ||
x <- unclass(x) | ||
out <- NextMethod(.Generic) | ||
if (summary_math){ | ||
out <- new_percent(out) | ||
} | ||
out | ||
} | ||
#' @export | ||
mean.percent <- function(x, ...){ | ||
new_percent(mean(unclass(x), ...)) | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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.
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.
Is it the entire description you want as just that 1 line? Not sure it fully reflects why users might want to use percents.
Edit: Nvm I think it's just line 6.
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.
Sorry, my 'suggestion' was just to remove the
\cr
at the end of line 6 which I assume is a typo?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.
Ah, no I typically use it to add a new line, but maybe there's better syntax for that.
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've checked and can't find an alternative that works surprisingly.
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
\cr
appears verbatim in the .Rd file (phsmethods/man/percent.Rd
Line 17 in b9a9151
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.
Maybe an obvious one but how do I get the documentation to display that line on a new line right below the previous text? If I insert a new line using Enter on my keyboard it places it 2 lines below.