From 8a1f540499727797b44d4ab491bdb115c93bfc71 Mon Sep 17 00:00:00 2001 From: kaz462 Date: Mon, 24 Jul 2023 21:27:01 -0700 Subject: [PATCH 1/8] #71: rounding draft --- posts/2023-07-24_rounding/appendix.R | 78 ++++++++++++++ posts/2023-07-24_rounding/rounding.png | Bin 0 -> 45734 bytes posts/2023-07-24_rounding/rounding.qmd | 142 +++++++++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 posts/2023-07-24_rounding/appendix.R create mode 100644 posts/2023-07-24_rounding/rounding.png create mode 100644 posts/2023-07-24_rounding/rounding.qmd diff --git a/posts/2023-07-24_rounding/appendix.R b/posts/2023-07-24_rounding/appendix.R new file mode 100644 index 00000000..946a007a --- /dev/null +++ b/posts/2023-07-24_rounding/appendix.R @@ -0,0 +1,78 @@ +# markdown helpers -------------------------------------------------------- + +markdown_appendix <- function(name, content) { + paste(paste("##", name, "{.appendix}"), " ", content, sep = "\n") +} +markdown_link <- function(text, path) { + paste0("[", text, "](", path, ")") +} + + + +# worker functions -------------------------------------------------------- + +insert_source <- function(repo_spec, name, + collection = "posts", + branch = "main", + host = "https://github.com", + text = "source code") { + path <- paste( + host, + repo_spec, + "tree", + branch, + collection, + name, + "index.qmd", + sep = "/" + ) + return(markdown_link(text, path)) +} + +insert_timestamp <- function(tzone = Sys.timezone()) { + time <- lubridate::now(tzone = tzone) + stamp <- as.character(time, tz = tzone, usetz = TRUE) + return(stamp) +} + +insert_lockfile <- function(repo_spec, name, + collection = "posts", + branch = "main", + host = "https://github.com", + text = "R environment") { + path <- paste( + host, + repo_spec, + "tree", + branch, + collection, + name, + "renv.lock", + sep = "/" + ) + return(markdown_link(text, path)) +} + + + +# top level function ------------------------------------------------------ + +insert_appendix <- function(repo_spec, name, collection = "posts") { + appendices <- paste( + markdown_appendix( + name = "Last updated", + content = insert_timestamp() + ), + " ", + markdown_appendix( + name = "Details", + content = paste( + insert_source(repo_spec, name, collection), + insert_lockfile(repo_spec, name, collection), + sep = ", " + ) + ), + sep = "\n" + ) + knitr::asis_output(appendices) +} diff --git a/posts/2023-07-24_rounding/rounding.png b/posts/2023-07-24_rounding/rounding.png new file mode 100644 index 0000000000000000000000000000000000000000..e442ae5bca41dbecb1e70b5565979b3a11b135c0 GIT binary patch literal 45734 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 A`v3p{ literal 0 HcmV?d00001 diff --git a/posts/2023-07-24_rounding/rounding.qmd b/posts/2023-07-24_rounding/rounding.qmd new file mode 100644 index 00000000..fcc2d7d7 --- /dev/null +++ b/posts/2023-07-24_rounding/rounding.qmd @@ -0,0 +1,142 @@ +--- +title: "Rounding" +author: + - name: Kangjie Zhang +description: "base R `round()` and round half up in R" +date: "2023-07-24" +# please do not use any non-default categories. +# You can find the default categories in the repository README.md +categories: [rounding] +# feel free to change the image +image: "rounding.png" +--- + + + +```{r setup, include=FALSE} +long_slug <- "2023-07-24_rounding" +# renv::use(lockfile = "renv.lock") +``` + + + +## Rounding methods + +Both SAS and base R have the function `round()`, which rounds the input to the specified number of decimal places. +However, they use different approaches when rounding off a 5: + +- SAS `round()` rounds half up (the common method of rounding) + +- base R `round()` [rounds to the nearest even](https://en.m.wikipedia.org/wiki/IEEE_754#Roundings_to_nearest) + +Although base R does not have the option for "round half up", there are functions available in other R packages (e.g., `janitor`, `tidytlg`). +The following table lists some often-used rounding methods and their corresponding functions in SAS and R. + ++---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ +| | round half up | round to even | round up | round down | round towards zero | ++:=============:+:======================================:+:==========================================:+:==========================================:+:==========================================:+:==========================================:+ +| SAS | `round()` | `rounde()` | `ceil()` | `floor()` | `int()` | ++---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ +| R | ::: {style="background-color: yellow"} | ::: {style="background-color: lightgreen"} | ::: {style="background-color: lightgreen"} | ::: {style="background-color: lightgreen"} | ::: {style="background-color: lightgreen"} | +| | `janitor::round_half_up()` | `round()` | `ceiling()` | `floor()` | `trunc()` | +| | | | | | | +| |
| {base} | {base} | {base} | {base} | +| | | ::: | ::: | ::: | ::: | +| | `tidytlg::roundSAS()` | | | | | +| | | | | | | +| |
| | | | | +| | ::: | | | | | ++---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ +| Example: 1.45 | 1.5 | 1.4 | 2 | 1 | 1 | +| | | | | | | +| | (round to 1 decimal place) | (round to 1 decimal place) | | | | ++---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ + +This table is summarized from links below, where more detailed discussions can be found - + +- Two SAS blogs about [round-to-even](https://blogs.sas.com/content/iml/2019/11/11/round-to-even.html) and [rounding-up-rounding-down](https://blogs.sas.com/content/iml/2011/10/03/rounding-up-rounding-down.html) + +- R documentation: Base R [Round](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/Round), [`janitor::round_half_up()`](https://sfirke.github.io/janitor/reference/round_half_up.html), [`tidytlg::roundSAS()`](https://pharmaverse.github.io/tidytlg/main/reference/roundSAS.html) + +- [CAMIS](https://psiaims.github.io/CAMIS/) (Comparing Analysis Method Implementations in Software) + +## Round half up in R + +The motivation for having a 'round half up' function is clear: it's a widely used rounding method, but there are no such options available in base R. + +There are multiple forums that discussed this topic, and a few functions available in some R packages. +Hope you won't need to spend much time identifying a safe option for rounding half up in R after reading the following section. + +### Bug fix + +The first time I needed to round half up in R, I chose the function from a [PHUSE paper](https://www.lexjansen.com/phuse-us/2020/ct/CT05.pdf) and applied it to my study. +It works fine most of the time, but has issues like the following example - + +```{r, message = FALSE} +# a function that rounds half up +# exact copy from: https://www.lexjansen.com/phuse-us/2020/ct/CT05.pdf +ut_round <- function(x, n=0) +{ + # x is the value to be rounded + # n is the precision of the rounding + scale <- 10^n + y <- trunc(x * scale + sign(x) * 0.5) / scale + # Return the rounded number + return(y) +} +# round half up for 2436.845, with 2 decimal places +ut_round(2436.845, 2) +``` + +The expected result is 2436.85, but the output rounds it down. +The function's logic seems fine, I was not able to find the root cause. +Thanks to the community effort, there are already discussions and resolution available in a [StackOverflow post](https://stackoverflow.com/questions/12688717/round-up-from-5#comment110611119_12688836) - + +> There are numerical precision issues, e.g., `round2(2436.845, 2)` returns `2436.84.` Changing `z + 0.5 to z + 0.5 + sqrt(.Machine$double.eps)` seems to work for me. -- Gregor Thomas Jun 24, 2020 at 2:16 + +After fixing this bug: + +```{r, message = FALSE} +# revised rounds half up +ut_round1 <- function(x, n=0) +{ + # x is the value to be rounded + # n is the precision of the rounding + scale <- 10^n + y <- trunc(x * scale + sign(x) * 0.5 + sqrt(.Machine$double.eps)) / scale + # Return the rounded number + return(y) +} +# round half up for 2436.845, with 2 decimal places +ut_round1(2436.845, 2) +``` + +### We are not alone + +The same issue occurred in the following ones as well, and has been raised by users: + +- `janitor::round_half_up()`: [issue](https://github.com/sfirke/janitor/issues/396) was raised and fixed in v2.1.0 + +- `Tplyr`: `options(tplyr.IBMRounding = TRUE)`, [issue](https://github.com/atorus-research/Tplyr/issues/124) was raised + +- `scrutiny::round_up_from()/round_up()`: [issue](https://lhdjung.github.io/scrutiny/reference/index.html#rounding) was raised + +- ... + +### Which one to use? + +The following two functions have the above bug fixed, they both came from the same [StackOverflow post](https://stackoverflow.com/questions/12688717/round-up-from-5). + +- [`janitor::round_half_up()`](https://sfirke.github.io/janitor/reference/round_half_up.html) **version \>= 2.1.0** +- [`tidytlg::roundSAS()`](https://pharmaverse.github.io/tidytlg/main/reference/roundSAS.html) + - this function has two more arguments that can convert the result to character and allow a character string to indicate missing values + + + +```{r, echo=FALSE} +source("appendix.R") +insert_appendix( + repo_spec = "pharmaverse/blog", + name = long_slug +) +``` From b129ca38f05b4d30c2afb95049880e9ba820a56b Mon Sep 17 00:00:00 2001 From: kaz462 Date: Mon, 31 Jul 2023 19:11:17 -0700 Subject: [PATCH 2/8] #71: address comments --- inst/WORDLIST.txt | 37 ++++++++ posts/2023-07-24_rounding/rounding.qmd | 118 ++++++++++++++----------- 2 files changed, 105 insertions(+), 50 deletions(-) diff --git a/inst/WORDLIST.txt b/inst/WORDLIST.txt index fa9a91d8..27290cb4 100644 --- a/inst/WORDLIST.txt +++ b/inst/WORDLIST.txt @@ -252,3 +252,40 @@ tlg TLG TLGs xpt +anderson +atorus +CAMIS +ceil +dn +doesn +edu +eps +gp +Gregor +htm +IBMRounding +iml +ji +lexjansen +lhdjung +lrcon +phuse +psiaims +rdocumentation +rounde +Roundings +roundSAS +rw +SAS's +sfirke +stackoverflow +StackOverflow +thm +tidytlg +tplyr +Tplyr +trunc +ucla +unv +ut +zMachine diff --git a/posts/2023-07-24_rounding/rounding.qmd b/posts/2023-07-24_rounding/rounding.qmd index fcc2d7d7..ad427ed9 100644 --- a/posts/2023-07-24_rounding/rounding.qmd +++ b/posts/2023-07-24_rounding/rounding.qmd @@ -2,7 +2,7 @@ title: "Rounding" author: - name: Kangjie Zhang -description: "base R `round()` and round half up in R" +description: "Exploration of some commonly used rounding methods and their corresponding functions in SAS and R, with a focus on 'round half up' and reliable solutions for numerical precision challenges" date: "2023-07-24" # please do not use any non-default categories. # You can find the default categories in the repository README.md @@ -25,32 +25,34 @@ long_slug <- "2023-07-24_rounding" Both SAS and base R have the function `round()`, which rounds the input to the specified number of decimal places. However, they use different approaches when rounding off a 5: -- SAS `round()` rounds half up (the common method of rounding) +- SAS `round()` [rounds half up](https://en.wikipedia.org/wiki/Rounding#Rounding_half_up). + This is the most common method of rounding. -- base R `round()` [rounds to the nearest even](https://en.m.wikipedia.org/wiki/IEEE_754#Roundings_to_nearest) +- base R `round()` [rounds to the nearest even](https://en.m.wikipedia.org/wiki/IEEE_754#Roundings_to_nearest), which matches SAS's `rounde()` function. Although base R does not have the option for "round half up", there are functions available in other R packages (e.g., `janitor`, `tidytlg`). + The following table lists some often-used rounding methods and their corresponding functions in SAS and R. -+---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ -| | round half up | round to even | round up | round down | round towards zero | -+:=============:+:======================================:+:==========================================:+:==========================================:+:==========================================:+:==========================================:+ -| SAS | `round()` | `rounde()` | `ceil()` | `floor()` | `int()` | -+---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ -| R | ::: {style="background-color: yellow"} | ::: {style="background-color: lightgreen"} | ::: {style="background-color: lightgreen"} | ::: {style="background-color: lightgreen"} | ::: {style="background-color: lightgreen"} | -| | `janitor::round_half_up()` | `round()` | `ceiling()` | `floor()` | `trunc()` | -| | | | | | | -| |
| {base} | {base} | {base} | {base} | -| | | ::: | ::: | ::: | ::: | -| | `tidytlg::roundSAS()` | | | | | -| | | | | | | -| |
| | | | | -| | ::: | | | | | -+---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ -| Example: 1.45 | 1.5 | 1.4 | 2 | 1 | 1 | -| | | | | | | -| | (round to 1 decimal place) | (round to 1 decimal place) | | | | -+---------------+----------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+--------------------------------------------+ ++---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ +| | round half up | round to even | round up | round down | round towards zero | ++:=============:+:======================================:+:==========================:+:=================:+:===============:+:==================:+ +| SAS | `round()` | `rounde()` | `ceil()` | `floor()` | `int()` | ++---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ +| R | ::: {style="background-color: yellow"} |
|
|
|
| +| | `janitor::round_half_up()` | | | | | +| | | `base::round()` | `base::ceiling()` | `base::floor()` | `base::trunc()` | +| |
| | | | | +| | |
|
|
|
| +| | `tidytlg::roundSAS()` | | | | | +| | | | | | | +| |
| | | | | +| | ::: | | | | | ++---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ +| Example: 1.45 | 1.5 | 1.4 | 2 | 1 | 1 | +| | | | | | | +| | (round to 1 decimal place) | (round to 1 decimal place) | | | | ++---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ This table is summarized from links below, where more detailed discussions can be found - @@ -58,54 +60,63 @@ This table is summarized from links below, where more detailed discussions can b - R documentation: Base R [Round](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/Round), [`janitor::round_half_up()`](https://sfirke.github.io/janitor/reference/round_half_up.html), [`tidytlg::roundSAS()`](https://pharmaverse.github.io/tidytlg/main/reference/roundSAS.html) -- [CAMIS](https://psiaims.github.io/CAMIS/) (Comparing Analysis Method Implementations in Software) +- [CAMIS](https://psiaims.github.io/CAMIS/) (Comparing Analysis Method Implementations in Software): A cross-industry initiative to document discrepant results between software. + [Rounding](https://psiaims.github.io/CAMIS/Comp/r-sas_rounding.html) is one of the comparisons, and there are much more [on this page](https://psiaims.github.io/CAMIS/)! ## Round half up in R The motivation for having a 'round half up' function is clear: it's a widely used rounding method, but there are no such options available in base R. -There are multiple forums that discussed this topic, and a few functions available in some R packages. -Hope you won't need to spend much time identifying a safe option for rounding half up in R after reading the following section. - -### Bug fix +There are multiple forums that have discussed this topic, and quite a few functions already available. +But which one to choose? The first time I needed to round half up in R, I chose the function from a [PHUSE paper](https://www.lexjansen.com/phuse-us/2020/ct/CT05.pdf) and applied it to my study. -It works fine most of the time, but has issues like the following example - +It works fine for a while until I encountered the following precision issue when double programming in R for TLGs made in SAS. + +### Numerical precision issue + +Example of rounding half up for 2436.845, with 2 decimal places: ```{r, message = FALSE} # a function that rounds half up # exact copy from: https://www.lexjansen.com/phuse-us/2020/ct/CT05.pdf -ut_round <- function(x, n=0) -{ - # x is the value to be rounded - # n is the precision of the rounding - scale <- 10^n - y <- trunc(x * scale + sign(x) * 0.5) / scale - # Return the rounded number - return(y) +ut_round <- function(x, n = 0) { + # x is the value to be rounded + # n is the precision of the rounding + scale <- 10^n + y <- trunc(x * scale + sign(x) * 0.5) / scale + # Return the rounded number + return(y) } # round half up for 2436.845, with 2 decimal places ut_round(2436.845, 2) ``` The expected result is 2436.85, but the output rounds it down. -The function's logic seems fine, I was not able to find the root cause. Thanks to the community effort, there are already discussions and resolution available in a [StackOverflow post](https://stackoverflow.com/questions/12688717/round-up-from-5#comment110611119_12688836) - > There are numerical precision issues, e.g., `round2(2436.845, 2)` returns `2436.84.` Changing `z + 0.5 to z + 0.5 + sqrt(.Machine$double.eps)` seems to work for me. -- Gregor Thomas Jun 24, 2020 at 2:16 -After fixing this bug: +- `.Machine$double.eps` is a built-in constant in R that represents the smallest positive floating-point number that can be represented on the system (reference: [Machine Characteristics](https://www.math.ucla.edu/~anderson/rw1001/library/base/html/zMachine.html)) + +- The expression `+ sqrt(.Machine$double.eps)` is used to add a very small value to mitigate floating-point precision issues. + +- For more information about computational precision and floating-point, see the following links - + + - R: [Why doesn't R think these numbers are equal?](https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-doesn_0027t-R-think-these-numbers-are-equal_003f) + - SAS: [Numerical Accuracy in SAS Software](https://documentation.sas.com/doc/en/lrcon/9.4/p0ji1unv6thm0dn1gp4t01a1u0g6.htm) + +After the fix: ```{r, message = FALSE} # revised rounds half up -ut_round1 <- function(x, n=0) -{ - # x is the value to be rounded - # n is the precision of the rounding - scale <- 10^n - y <- trunc(x * scale + sign(x) * 0.5 + sqrt(.Machine$double.eps)) / scale - # Return the rounded number - return(y) +ut_round1 <- function(x, n = 0) { + # x is the value to be rounded + # n is the precision of the rounding + scale <- 10^n + y <- trunc(x * scale + sign(x) * 0.5 + sqrt(.Machine$double.eps)) / scale + # Return the rounded number + return(y) } # round half up for 2436.845, with 2 decimal places ut_round1(2436.845, 2) @@ -113,23 +124,30 @@ ut_round1(2436.845, 2) ### We are not alone -The same issue occurred in the following ones as well, and has been raised by users: +The same issue occurred in the following functions/options as well, and has been raised by users: - `janitor::round_half_up()`: [issue](https://github.com/sfirke/janitor/issues/396) was raised and fixed in v2.1.0 - `Tplyr`: `options(tplyr.IBMRounding = TRUE)`, [issue](https://github.com/atorus-research/Tplyr/issues/124) was raised -- `scrutiny::round_up_from()/round_up()`: [issue](https://lhdjung.github.io/scrutiny/reference/index.html#rounding) was raised +- `scrutiny::round_up_from()/round_up()`: [issue](https://github.com/lhdjung/scrutiny/issues/43) was raised and fixed -- ... +- \... + and many others! ### Which one to use? -The following two functions have the above bug fixed, they both came from the same [StackOverflow post](https://stackoverflow.com/questions/12688717/round-up-from-5). +After reading this post, I hope it will help you save some time identifying which functions are safe to use for rounding half up. + +The following functions have the precision issue mentioned above fixed, they all share the same logic from this [StackOverflow post](https://stackoverflow.com/questions/12688717/round-up-from-5). - [`janitor::round_half_up()`](https://sfirke.github.io/janitor/reference/round_half_up.html) **version \>= 2.1.0** - [`tidytlg::roundSAS()`](https://pharmaverse.github.io/tidytlg/main/reference/roundSAS.html) - this function has two more arguments that can convert the result to character and allow a character string to indicate missing values +- [`scrutiny::round_up_from()/round_up()`](https://lhdjung.github.io/scrutiny/reference/rounding-common.html) **version \>= 0.2.5** + - `round_up_from()` has a `threshold` argument for rounding up, which adds flexibility for rounding up + + - `round_up()` rounds up from 5, which is a special case of `round_up_from()` From 448d19852e800094c8ae6f8e8a80887b25e63de7 Mon Sep 17 00:00:00 2001 From: kaz462 Date: Wed, 2 Aug 2023 11:58:27 -0700 Subject: [PATCH 3/8] #71: split the summary table --- posts/2023-07-24_rounding/rounding.qmd | 50 +++++++++++++++----------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/posts/2023-07-24_rounding/rounding.qmd b/posts/2023-07-24_rounding/rounding.qmd index ad427ed9..6fec7bc6 100644 --- a/posts/2023-07-24_rounding/rounding.qmd +++ b/posts/2023-07-24_rounding/rounding.qmd @@ -32,27 +32,35 @@ However, they use different approaches when rounding off a 5: Although base R does not have the option for "round half up", there are functions available in other R packages (e.g., `janitor`, `tidytlg`). -The following table lists some often-used rounding methods and their corresponding functions in SAS and R. - -+---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ -| | round half up | round to even | round up | round down | round towards zero | -+:=============:+:======================================:+:==========================:+:=================:+:===============:+:==================:+ -| SAS | `round()` | `rounde()` | `ceil()` | `floor()` | `int()` | -+---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ -| R | ::: {style="background-color: yellow"} |
|
|
|
| -| | `janitor::round_half_up()` | | | | | -| | | `base::round()` | `base::ceiling()` | `base::floor()` | `base::trunc()` | -| |
| | | | | -| | |
|
|
|
| -| | `tidytlg::roundSAS()` | | | | | -| | | | | | | -| |
| | | | | -| | ::: | | | | | -+---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ -| Example: 1.45 | 1.5 | 1.4 | 2 | 1 | 1 | -| | | | | | | -| | (round to 1 decimal place) | (round to 1 decimal place) | | | | -+---------------+----------------------------------------+----------------------------+-------------------+-----------------+--------------------+ + +In general, there are many often used rounding methods. In the table below, you can find examples of them applied to the number 1.45. + ++---------------+----------------------------+----------------------------+----------+------------+--------------------+ +| | round half up | round to even | round up | round down | round towards zero | ++:=============:+:==========================:+:==========================:+:========:+:==========:+:==================:+ +| Example: 1.45 | 1.5 | 1.4 | 2 | 1 | 1 | +| | | | | | | +| | (round to 1 decimal place) | (round to 1 decimal place) | | | | ++---------------+----------------------------+----------------------------+----------+------------+--------------------+ + +Here are the corresponding ways to implement these methods in SAS and R. + ++---------+----------------------------------------+-----------------+-------------------+-----------------+--------------------+ +| | round half up | round to even | round up | round down | round towards zero | ++:=======:+:======================================:+:===============:+:=================:+:===============:+:==================:+ +| SAS | `round()` | `rounde()` | `ceil()` | `floor()` | `int()` | ++---------+----------------------------------------+-----------------+-------------------+-----------------+--------------------+ +| R | ::: {style="background-color: yellow"} |
|
|
|
| +| | `janitor::round_half_up()` | | | | | +| | | `base::round()` | `base::ceiling()` | `base::floor()` | `base::trunc()` | +| |
| | | | | +| | |
|
|
|
| +| | `tidytlg::roundSAS()` | | | | | +| | | | | | | +| |
| | | | | +| | ::: | | | | | ++---------+----------------------------------------+-----------------+-------------------+-----------------+--------------------+ + This table is summarized from links below, where more detailed discussions can be found - From c973204eba88e8424d52df86e1e863b6a821496a Mon Sep 17 00:00:00 2001 From: kaz462 Date: Wed, 2 Aug 2023 12:03:25 -0700 Subject: [PATCH 4/8] #71: update .lycheeignore --- .lycheeignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.lycheeignore b/.lycheeignore index cdb85a26..9f57b8a6 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -5,3 +5,4 @@ https://www.linkedin.com/* https://cosa.cdisc.org/events/Admiral ///home/runner/work/blog/blog/posts/2023-06-28_welcome/pharmaverse.slack.com https://pharmaverse.github.io/blog/posts/2023-06-27_hackathon_app/ +https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-doesn_0027t-R-think-these-numbers-are-equal_003f From baae1b90ee763aeca6ac17fbbe26b249b890294d Mon Sep 17 00:00:00 2001 From: kaz462 Date: Mon, 21 Aug 2023 18:16:24 -0700 Subject: [PATCH 5/8] #71: update the last section --- posts/2023-07-24_rounding/rounding.qmd | 67 ++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/posts/2023-07-24_rounding/rounding.qmd b/posts/2023-07-24_rounding/rounding.qmd index 6fec7bc6..1eff46b2 100644 --- a/posts/2023-07-24_rounding/rounding.qmd +++ b/posts/2023-07-24_rounding/rounding.qmd @@ -32,8 +32,8 @@ However, they use different approaches when rounding off a 5: Although base R does not have the option for "round half up", there are functions available in other R packages (e.g., `janitor`, `tidytlg`). - -In general, there are many often used rounding methods. In the table below, you can find examples of them applied to the number 1.45. +In general, there are many often used rounding methods. +In the table below, you can find examples of them applied to the number 1.45. +---------------+----------------------------+----------------------------+----------+------------+--------------------+ | | round half up | round to even | round up | round down | round towards zero | @@ -53,15 +53,16 @@ Here are the corresponding ways to implement these methods in SAS and R. | R | ::: {style="background-color: yellow"} |
|
|
|
| | | `janitor::round_half_up()` | | | | | | | | `base::round()` | `base::ceiling()` | `base::floor()` | `base::trunc()` | -| |
| | | | | -| | |
|
|
|
| +| | ::: {style="background-color: yellow"} | | | | | +| |
|
|
| | | +| | | | | | | | | `tidytlg::roundSAS()` | | | | | | | | | | | | | | | | | | | | | ::: | | | | | +| | ::: | | | | | +---------+----------------------------------------+-----------------+-------------------+-----------------+--------------------+ - This table is summarized from links below, where more detailed discussions can be found - - Two SAS blogs about [round-to-even](https://blogs.sas.com/content/iml/2019/11/11/round-to-even.html) and [rounding-up-rounding-down](https://blogs.sas.com/content/iml/2011/10/03/rounding-up-rounding-down.html) @@ -76,7 +77,8 @@ This table is summarized from links below, where more detailed discussions can b The motivation for having a 'round half up' function is clear: it's a widely used rounding method, but there are no such options available in base R. There are multiple forums that have discussed this topic, and quite a few functions already available. -But which one to choose? +But which ones to choose? +Are they safe options? The first time I needed to round half up in R, I chose the function from a [PHUSE paper](https://www.lexjansen.com/phuse-us/2020/ct/CT05.pdf) and applied it to my study. It works fine for a while until I encountered the following precision issue when double programming in R for TLGs made in SAS. @@ -143,9 +145,7 @@ The same issue occurred in the following functions/options as well, and has been - \... and many others! -### Which one to use? - -After reading this post, I hope it will help you save some time identifying which functions are safe to use for rounding half up. +### Which ones to use? The following functions have the precision issue mentioned above fixed, they all share the same logic from this [StackOverflow post](https://stackoverflow.com/questions/12688717/round-up-from-5). @@ -157,6 +157,55 @@ The following functions have the precision issue mentioned above fixed, they all - `round_up()` rounds up from 5, which is a special case of `round_up_from()` +### Are they safe options? + +Those "round half up" functions do not offer the same level of precision and accuracy as the base R round function. + +For example, let's consider a value `a` that is slightly less than `1.5`. +If we choose round half up approach to round `a` to 0 decimal places, an output of `1` is expected. +However, those functions yield a result of `2` because `1.5 - a` is less than `sqrt(.Machine$double.eps)`. + +```{r, message = FALSE} +a <- 1.5 - 0.5*sqrt(.Machine$double.eps) +ut_round1(a, 0) +janitor::round_half_up(a, digits = 0) +``` + +This behavior aligns the floating point number comparison functions `all.equal()` and `dplyr::near()` with default tolerance `.Machine$double.eps^0.5`, where 1.5 and `a` are treated as equal. + +```{r, message = FALSE} +all.equal(a, 1.5) +dplyr::near(a, 1.5) +``` + +We can get the expected results from base R `round` as it provides greater accuracy. + +```{r, message = FALSE} +round(a) +``` + +Here is an example when base R `round` reaches the precision limit: + +```{r, message=FALSE} +# b is slightly less than 1.5 +b <- 1.5 - 0.5*.Machine$double.eps +# 1 is expected but the result is 2 +round(b) +``` + +The precision and accuracy requirements can vary depending on the application. +Therefore, it is essential to be aware each function's performance in your specific context before making a choice. + +## Conclusion + +> With the differences in default behaviour across languages, you could consider your QC strategy and whether an acceptable level of fuzz in the electronic comparisons could be allowed for cases such as rounding when making comparisons between 2 codes written in different languages as long as this is documented. +> Alternatively you could document the exact rounding approach to be used in the SAP and then match this regardless of programming language used. +> - Ross Farrugia + +Thanks Ross Farrugia, Ben Straub, Edoardo Mancini and Liming for reviewing this blog post and providing valuable feedback! + +If you spot an issue or have different opinions, please don't hesitate to raise them through [pharmaverse/blog](https://github.com/pharmaverse/blog)! + ```{r, echo=FALSE} From 3abb047a7c99f43b1badfe9c29ccdcafe6472b44 Mon Sep 17 00:00:00 2001 From: kaz462 Date: Mon, 21 Aug 2023 18:23:05 -0700 Subject: [PATCH 6/8] #71: update wordlist --- inst/WORDLIST.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/inst/WORDLIST.txt b/inst/WORDLIST.txt index aefbf9c6..d2b5ee0c 100644 --- a/inst/WORDLIST.txt +++ b/inst/WORDLIST.txt @@ -321,3 +321,5 @@ dy lubridate TRTSDTM ymd +behaviour +Farrugia From 53a59926f6a2da26f1cd0d312298453027a85c58 Mon Sep 17 00:00:00 2001 From: kaz462 Date: Mon, 21 Aug 2023 18:28:43 -0700 Subject: [PATCH 7/8] #71: styler/link --- posts/2023-07-24_rounding/rounding.qmd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/posts/2023-07-24_rounding/rounding.qmd b/posts/2023-07-24_rounding/rounding.qmd index 1eff46b2..6a1ad0e6 100644 --- a/posts/2023-07-24_rounding/rounding.qmd +++ b/posts/2023-07-24_rounding/rounding.qmd @@ -67,7 +67,7 @@ This table is summarized from links below, where more detailed discussions can b - Two SAS blogs about [round-to-even](https://blogs.sas.com/content/iml/2019/11/11/round-to-even.html) and [rounding-up-rounding-down](https://blogs.sas.com/content/iml/2011/10/03/rounding-up-rounding-down.html) -- R documentation: Base R [Round](https://www.rdocumentation.org/packages/base/versions/3.6.2/topics/Round), [`janitor::round_half_up()`](https://sfirke.github.io/janitor/reference/round_half_up.html), [`tidytlg::roundSAS()`](https://pharmaverse.github.io/tidytlg/main/reference/roundSAS.html) +- R documentation: Base R [Round](https://stat.ethz.ch/R-manual/R-devel/library/base/html/Round.html), [`janitor::round_half_up()`](https://sfirke.github.io/janitor/reference/round_half_up.html), [`tidytlg::roundSAS()`](https://pharmaverse.github.io/tidytlg/main/reference/roundSAS.html) - [CAMIS](https://psiaims.github.io/CAMIS/) (Comparing Analysis Method Implementations in Software): A cross-industry initiative to document discrepant results between software. [Rounding](https://psiaims.github.io/CAMIS/Comp/r-sas_rounding.html) is one of the comparisons, and there are much more [on this page](https://psiaims.github.io/CAMIS/)! @@ -166,7 +166,7 @@ If we choose round half up approach to round `a` to 0 decimal places, an output However, those functions yield a result of `2` because `1.5 - a` is less than `sqrt(.Machine$double.eps)`. ```{r, message = FALSE} -a <- 1.5 - 0.5*sqrt(.Machine$double.eps) +a <- 1.5 - 0.5 * sqrt(.Machine$double.eps) ut_round1(a, 0) janitor::round_half_up(a, digits = 0) ``` @@ -188,8 +188,8 @@ Here is an example when base R `round` reaches the precision limit: ```{r, message=FALSE} # b is slightly less than 1.5 -b <- 1.5 - 0.5*.Machine$double.eps -# 1 is expected but the result is 2 +b <- 1.5 - 0.5 * .Machine$double.eps +# 1 is expected but the result is 2 round(b) ``` From 1a031b6e09c4669b3c67aeb7884aa102ea21f453 Mon Sep 17 00:00:00 2001 From: kaz462 Date: Mon, 21 Aug 2023 18:30:32 -0700 Subject: [PATCH 8/8] #71: update wordlist --- inst/WORDLIST.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/inst/WORDLIST.txt b/inst/WORDLIST.txt index d2b5ee0c..e14029fe 100644 --- a/inst/WORDLIST.txt +++ b/inst/WORDLIST.txt @@ -323,3 +323,4 @@ TRTSDTM ymd behaviour Farrugia +ethz