Skip to content
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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,5 @@ Encoding: UTF-8
Language: en-GB
LazyData: true
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
RoxygenNote: 7.3.2
VignetteBuilder: knitr
10 changes: 10 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,8 +1,18 @@
# Generated by roxygen2: do not edit by hand

S3method("[",percent)
S3method(Math,percent)
S3method(Summary,percent)
S3method(as.character,percent)
S3method(format,percent)
S3method(mean,percent)
S3method(print,percent)
S3method(rep,percent)
S3method(unique,percent)
export(age_calculate)
export(age_from_chi)
export(age_group)
export(as_percent)
export(chi_check)
export(chi_pad)
export(create_age_groups)
Expand Down
210 changes: 210 additions & 0 deletions R/percent.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
#' Percentages
#'
#' @description
#'
#' `percent` is a lightweight S3 class allowing for pretty
#' printing of proportions as percentages. \cr
Moohan marked this conversation as resolved.
Show resolved Hide resolved
#' 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
#' # 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 (!(identical(class(x), "integer") || identical(class(x), "numeric"))){
cli::cli_abort("{.arg x} must be a {.cls numeric} vector, not a {.cls {class(x)}} vector.")
}
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
}
Moohan marked this conversation as resolved.
Show resolved Hide resolved

#' @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 (switch(.Generic, `sign` = TRUE, FALSE)){
NextMethod(.Generic)
} else 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)
}
}
#' @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), ...))
}
69 changes: 69 additions & 0 deletions man/percent.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions man/phsmethods.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading