Lazy evaluation in R leads to unexpected results if the environment changes between the time the object was created and the time it is evaluated

Viewed 93

openxlsx in its current version 4.2.4 overwrites digits precision from default 7 to 22. This results in unexpected behavior, if you try to write a lazy evaluating data.frame using writeData.

library(openxlsx)
library(magrittr)
library(dplyr)

wb <- createWorkbook()
sheet <- addWorksheet(wb,"Sheet")
data.frame(a=55.7) %>% mutate(a=a%>%format(nsmall=1)) %>% writeData(wb,sheet,.)
saveWorkbook(wb,"test.xlsx")

You may see, that the workbook contains not the expected string "55.7", but more decimals. This is because in global environment format works with 7 digits, so format(55.7)=="55.7", but within writeData it evaluates as

format(55.7)=="55.700000000000003"

This is not a issue of floating-point math, but an issue of changing environments. format uses as a default value "digits=getOption("digits")". Default is 7, within writeData it's 22. That's all there is, nothin special about that.

I wanted to know, why format(55.7) does not get evaluated within the context of global environment.

2 Answers

I guess it's a feature (or bug) of openxlsx:

  • The R objects were identical
  • This is not an issue of displaying or formatting. I looked into the XML of these files and the numbers 55.70000000000000284217 and 57.7 were written by the R function
  • Replacing openxlsx with writexl eliminates this discrepancy

The expression is lazily evaluated with the digits options in the current environment. openxlsx::writeData somewhere uses 17 digits, whereas the default value is 7.

library(magrittr)

f <- function(x) {
  rlang::scoped_options(digits=17)
  x$a
}
data.frame(a=format(1234.1)) %>% f()

> 1234.0999999999999

df <- data.frame(a=format(1234.1))
df %>% f()

> 1234.1

Edit: Developers from openxlsx confirmed the behaviour. They solved it by forcing the evaluation of their input data.frame before changing any environment variables in their fix.


Here is the link to my next question which shows the exact same unexpected behavior, but without using numbers.

Related