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

Continuous variables will not print from svyCreateTableOne #83

Open
stjeandenise opened this issue Aug 5, 2021 · 7 comments
Open

Continuous variables will not print from svyCreateTableOne #83

stjeandenise opened this issue Aug 5, 2021 · 7 comments

Comments

@stjeandenise
Copy link

Categorical variable work fine but any attempt to print continuous variables yields the following error:

Error in round(n, digits = digits) : non-numeric argument to mathematical function

Someone reported the same exact issue on Stack Overflow this week and was able to fix it using RTools. However, RTools is specific to Windows and I am a Mac user so I do not know how to proceed. Is there a bug on the developer end causing this issue?

@ndevln
Copy link
Contributor

ndevln commented Aug 7, 2021

Hi,

it is quite unlikely that installing RTools fixed this issue. RTools is mostly an installer for gcc (C compiler) and gfortran for Windows. MacOS comes with clang (C compiler) so this step is not needed. Also, this package (tableone) does not use C Code, so there is no reason this should be a problem.

Since the r-base function round() is the problem, you could try calling round on every numeric column you have and see if it fails:

round(data$x)

or you could call CreateTableOne with a one column dataframe:

library(tidyverse)
library(tableone)
library(survival)
data(pbc)

tableOne <- CreateTableOne(vars = c("copper"), data = pbc %>% select(copper))

This could help narrow down the problem. Does it affect all continuous columns?

@stjeandenise
Copy link
Author

hi,

r-base round function works just fine for my column. and this is specifically for "svyCreateTableOne" rather than "CreateTableOne". there are no issues with the original CreateTableOne. while i can use the svyCreateTableOne function just fine to create the table, an error appears on printing the results if a continuous variable is included. there is no error actually using the svyCreateTableOne function and no error printing if all variables are categorical.

so to continue your example:

library(tidyverse)
library(tableone)
library(survival)
data(pbc)

tab1_svy <- svydesign(id = ~1, data = pbc, weights = ~wgt)

tab1weighted <- svyCreateTableOne(vars = c("copper"), data = tab1_svy)

tab1weighted

print(tab1weighted)

^ the last 2 lines above will both result in the rounding error while there are no errors for the first 2 lines.

@tszberkowitz
Copy link
Contributor

I recently encountered the same error as @stjeandenise when trying to print the object created by the svyCreateTableOne() function: Error in round(n, digits = digits) : non-numeric argument to mathematical function

Based on my amateur sleuthing, I also agree with @ndevln that RTools is probably irrelevant for this particular issue. Reproducible example based on the one from previous posts:

### Load packages
library(tidyverse)
library(tableone)
library(survival)
library(survey)

### Load dataframe
data(pbc)

### Create working copy of dataframe
pbc2 <- pbc

### Generate random weights
set.seed(831)
pbc2$wgt <- rnorm(n = nrow(pbc2), mean = 1, sd = 0.1)

### Create weighted Table 1 object (no errors)
tab1_svy <- svydesign(id = ~1, data = pbc2, weights = ~wgt)
tab1weighted <- svyCreateTableOne(vars = c("copper"), data = tab1_svy)

### Attempt to print weighted Table 1 object (throws an error)
tab1weighted

### Attempt to explicitly print weighted Table 1 object (still throws an error)
print(tab1weighted)

Short answer: The error occurs due to how the tableone:::print.svyContTable() method tries to pull the sample size from the svyTableOne object. Unfortunately, I do not know enough to be able to understand exactly why this is happening or how to fix it.

Long answer:

The problem is not that the tableone code isn't creating the object (it is creating one that appears to be correct), the problem occurs when R tries to print the object. Running str(tab1weighted) shows that there is definitely a weighted Table 1 object in the workspace.

So why does this happen now when it didn't happen before? My guess is that something changed between when I last ran my code (where it worked without issue) and when I recently re-ran the same code (where I got the error). However, I have found it rather difficult to determine exactly what change caused this to happen.

At my job I have access to a couple different instances of R/RStudio, one of which is more locked down (shared server) than the other (local install). In other words, I can quickly check different package versions locally but not on the server (which is where I initially encountered the error). However, I encountered a strange problem when I went to create and run a reproducible example where the error mentioned above is generated: I couldn't get R to generate any errors when printing weighted Table 1 objects.

When I ran the reprex above in my local install of R/RStudio, the code worked as expected:

tab1weighted
#                     
#                      Overall       
#   n                  419.06        
#   copper (mean (SD))  97.43 (85.78)

No errors, no warnings, and output looks correct. OK, so it must be a version issue, right? R, RStudio, or one or more packages are probably different between the 2 environments where I am running things. (Note that I doubt that RStudio has anything to do with this issue but I'm not ruling it out yet.) Here are my initial local and remote (server) R/RStudio environment versions:

Version Local Remote
OS Windows 10 x64 (build 19043) Windows Server 2012 R2 x64 (build 9600)
R 4.0.3 4.0.5
RStudio 1.3.1093 1.4.1717
Package tableone 0.12.0 0.13.0
Package survey 4.0 4.1-1

So the reprex works without issue when using tableone v. 0.12.0 and survey v. 4.0 on R v. 4.0.3 on Windows 10 (64-bit) but it throws the error when using tableone v. 0.13.0 and survey v. 4.1-1 on R v. 4.0.5 on Windows Server 2012 R2 (64-bit). Since I can't change any package versions in the remote environment, I started changing my local R package versions and re-running the reprex. (I am unable to update the installation of R/RStudio in either my local or remote environment without a substantial delay so all my local work was run on R v. 4.0.3.)

### No package updates -----
library(tidyverse)
library(tableone)
library(survival)
library(survey)
data(pbc)

pbc2 <- pbc
set.seed(831)
pbc2$wgt <- rnorm(n = nrow(pbc2), mean = 1, sd = 0.1)
tab1_svy <- svydesign(id = ~1, data = pbc2, weights = ~wgt)
tab1weighted <- svyCreateTableOne(vars = c("copper"), data = tab1_svy)
tab1weighted
print(tab1weighted)

getRversion(); packageVersion('tableone'); packageVersion('survey')
### R (4.0.3), tableone (0.12.0), survey (4.0)
### RESULT: No errors, everything works as expected.



### Only tableone updated (0.12.0 -> 0.13.0) -----
library(tidyverse)
library(tableone)
library(survival)
library(survey)
data(pbc)

pbc2 <- pbc
set.seed(831)
pbc2$wgt <- rnorm(n = nrow(pbc2), mean = 1, sd = 0.1)
tab1_svy <- svydesign(id = ~1, data = pbc2, weights = ~wgt)
tab1weighted <- svyCreateTableOne(vars = c("copper"), data = tab1_svy)
tab1weighted
print(tab1weighted)

getRversion(); packageVersion('tableone'); packageVersion('survey')
### R (4.0.3), tableone (0.13.0), survey (4.0)
### RESULT: Error message when **creating** `tab1weighted` with the `svyCreateTableOne` function.
### Error in UseMethod("oldsvyquantile", design) : 
###   no applicable method for 'oldsvyquantile' applied to an object of class "c('survey.design2', 'survey.design')"



### Only survey updated (4.0 -> 4.1-1) -----
library(tidyverse)
library(tableone)
library(survival)
library(survey)
data(pbc)

pbc2 <- pbc
set.seed(831)
pbc2$wgt <- rnorm(n = nrow(pbc2), mean = 1, sd = 0.1)
tab1_svy <- svydesign(id = ~1, data = pbc2, weights = ~wgt)
tab1weighted <- svyCreateTableOne(vars = c("copper"), data = tab1_svy)
tab1weighted
print(tab1weighted)

getRversion(); packageVersion('tableone'); packageVersion('survey')
### R (4.0.3), tableone (0.12.0), survey (4.1-1)
### RESULT: Error message when **printing** `tab1weighted` object.
### Error in round(n, digits = digits) : 
###     non-numeric argument to mathematical function



### Both tableone (0.12.0 -> 0.13.0) and survey (4.0 -> 4.1-1) updated -----
library(tidyverse)
library(tableone)
library(survival)
library(survey)
data(pbc)

pbc2 <- pbc
set.seed(831)
pbc2$wgt <- rnorm(n = nrow(pbc2), mean = 1, sd = 0.1)
tab1_svy <- svydesign(id = ~1, data = pbc2, weights = ~wgt)
tab1weighted <- svyCreateTableOne(vars = c("copper"), data = tab1_svy)
tab1weighted
print(tab1weighted)

getRversion(); packageVersion('tableone'); packageVersion('survey')
### R (4.0.3), tableone (0.13.0), survey (4.1-1)
### RESULT: No errors, everything works as expected.

In short:

Packages updated Weighted Table 1 object created? Weighted Table 1 object printed?
None Yes Yes
tableone (0.12.0 -> 0.13.0) No --
survey (4.0 -> 4.1-1) Yes No
tableone (0.12.0 -> 0.13.0) and survey (4.0 -> 4.1-1) Yes Yes

...wait. Why did the scenario with tableone v. 0.13.0 and survey v. 4.1-1 (both the most recent versions for Windows on CRAN at the time of my tests) work without errors? Those are the same package versions as are located in the remote environment where I was getting the error.

I was able to get that exact same error when printing the weighted Table 1 object locally but only when I had updated just the survey package (4.0 -> 4.1-1) and left the tableone package at v. 0.12.0; as described above, updating both packages in my local install (to match the versions in the remote install) failed to generate the expected error. Updating only the tableone package (0.12.0 -> 0.13.0) and leaving the survey package at v. 4.0 gave me a different error that I had never seen before.

Just to be sure, I loaded the reprex to the remote environment and confirmed that it still generated the error there and the packages available there are definitely still the most recent versions for Windows published to CRAN (at the time of my tests), v. 0.13.0 for tableone and v. 4.1-1 for survey.

So having the latest packages in R v. 4.0.3 didn't result in any errors but having the latest packages in R v. 4.0.5 did? Did something change between 4.0.3 and 4.0.5? Maybe something to do with the round() function?

According to R News (accessed 2021-09-01), in the "Bug Fixes" section under "CHANGES IN R 4.0.4" there is an item stating:

round() and signif() no longer tolerate wrong argument names, notably in 1-argument calls; reported by Shane Mueller on R-devel (mailing list); later reported as PR#17976.

Unfortunately, due to workplace hyperlink restrictions I am unable to view the referenced pull request so I can't tell if it is likely (or even possible) for this bug fix to have had led to the error that I have been encountering when trying to print weighted Table 1s.

The bug fix involving round() might even be irrelevant, though, because I believe that I traced the source of the error to the tableone:::print.svyContTable() method. Specifically, the offending line noted in the code below, which is an excerpt from the section of the tableone:::print.svyContTable() method's source code where the internal object strataN is created:

tableone:::print.svyContTable
# ... truncated ...
  strataN <- sapply(ContTable, FUN = function(stratum) {
        n <- stratum[, "n"]
        n[!is.null(n)][1]
        n <- ifelse(is.null(n), "0", n)
        n <- round(n, digits = digits)      ### <-- this line throws the error!
        n <- do.call(base::format, c(list(x = n, trim = TRUE), 
            formatOptions))
        as.character(n)
    }, simplify = TRUE)
# ... truncated ...

When I attempted to step through this code manually (using the tab1weighted object from the reprex), I was able to determine that the error is being caused by the fact that the object n is a list of length 1 containing a vector of length 1 containing the (weighted) sample size. This list can be printed in R for the object from the reprex by running tab1weighted$ContTable[[1]][, "n"]; passing this list to the round() function throws the same error that OP and I have both received:

round(tab1weighted$ContTable[[1]][, "n"])
# Error in round(tab1weighted$ContTable[[1]][, "n"]) : 
#   non-numeric argument to mathematical function

Finally, I compared the tab1weighted object produced by running the reprex with only the survey package updated (4.0 -> 4.1-1) with the tab1weighted object produced by running the reprex after updating both the tableone and survey packages, 0.12.0 -> 0.13.0 and 4.0 -> 4.1-1, respectively. (Note that this was all done locally.)

I found that trying to print the "bad" tab1weighted object (the one produced with tableone v. 0.12.0 and survey v. 4.1-1) still generated the error, even after I had updated the local tableone package to v. 0.13.0:

### "Bad" version
badtab1weighted <- structure(list(Overall = structure(list(419.055217809267, 108.39511607445, 
    25.8665472872804, 97.433390063506, 85.7825101034694, structure(c(73, 
    67, 79, 3.04929042994627), .Dim = c(1L, 4L), .Dimnames = list(
        "0.5", c("quantile", "ci.2.5", "ci.97.5", "se"))), structure(c(41, 
    38, 48, 2.54107535828856), .Dim = c(1L, 4L), .Dimnames = list(
        "0.25", c("quantile", "ci.2.5", "ci.97.5", "se"))), structure(c(123, 
    110, 148, 9.65608636149652), .Dim = c(1L, 4L), .Dimnames = list(
        "0.75", c("quantile", "ci.2.5", "ci.97.5", "se"))), structure(c(9, 
    NaN, 12, NaN), .Dim = c(1L, 4L), .Dimnames = list("0", c("quantile", 
    "ci.2.5", "ci.97.5", "se"))), structure(c(588, 588, 588, 
    0), .Dim = c(1L, 4L), .Dimnames = list("1", c("quantile", 
    "ci.2.5", "ci.97.5", "se")))), .Dim = c(1L, 10L), .Dimnames = list(
    "copper", c("n", "miss", "p.miss", "mean", "sd", "median", 
    "p25", "p75", "min", "max")))), class = c("svyContTable", 
"ContTable", "by"), percentMissing = c(copper = 25.8373205741627))
print(badtab1weighted)
# Error in round(n, digits = digits) : 
#   non-numeric argument to mathematical function


### "Good" version
goodtab1weighted <- structure(list(Overall = structure(c(419.055217809267, 108.39511607445, 
25.8665472872804, 97.433390063506, 85.7825101034694, 73, 41, 
123, 4, 588), .Dim = c(1L, 10L), .Dimnames = list("copper", c("n", 
"miss", "p.miss", "mean", "sd", "median", "p25", "p75", "min", 
"max")))), class = c("svyContTable", "ContTable", "by"), percentMissing = c(copper = 25.8373205741627))
print(goodtab1weighted)
#                     
#                      Overall       
#   n                  419.06        
#   copper (mean (SD))  97.43 (85.78)

When I compared the structures of the 2 objects, I noticed that the CatTable and MetaData elements of the 2 objects were identical but the ContTable element was structured slightly differently:

### "Bad" version
str(badtab1weighted)
# List of 3
#  $ ContTable:List of 1
#   ..$ Overall:List of 10
#   .. ..$ : num 419
#   .. ..$ : num 108
#   .. ..$ : num 25.9
#   .. ..$ : num 97.4
#   .. ..$ : num 85.8
#   .. ..$ : num [1, 1:4] 73 67 79 3.05
#   .. .. ..- attr(*, "dimnames")=List of 2
#   .. .. .. ..$ : chr "0.5"
#   .. .. .. ..$ : chr [1:4] "quantile" "ci.2.5" "ci.97.5" "se"
#   .. ..$ : num [1, 1:4] 41 38 48 2.54
#   .. .. ..- attr(*, "dimnames")=List of 2
#   .. .. .. ..$ : chr "0.25"
#   .. .. .. ..$ : chr [1:4] "quantile" "ci.2.5" "ci.97.5" "se"
#   .. ..$ : num [1, 1:4] 123 110 148 9.66
#   .. .. ..- attr(*, "dimnames")=List of 2
#   .. .. .. ..$ : chr "0.75"
#   .. .. .. ..$ : chr [1:4] "quantile" "ci.2.5" "ci.97.5" "se"
#   .. ..$ : num [1, 1:4] 9 NaN 12 NaN
#   .. .. ..- attr(*, "dimnames")=List of 2
#   .. .. .. ..$ : chr "0"
#   .. .. .. ..$ : chr [1:4] "quantile" "ci.2.5" "ci.97.5" "se"
#   .. ..$ : num [1, 1:4] 588 588 588 0
#   .. .. ..- attr(*, "dimnames")=List of 2
#   .. .. .. ..$ : chr "1"
#   .. .. .. ..$ : chr [1:4] "quantile" "ci.2.5" "ci.97.5" "se"
#   .. ..- attr(*, "dim")= int [1:2] 1 10
#   .. ..- attr(*, "dimnames")=List of 2
#   .. .. ..$ : chr "copper"
#   .. .. ..$ : chr [1:10] "n" "miss" "p.miss" "mean" ...
#   ..- attr(*, "class")= chr [1:3] "svyContTable" "ContTable" "by"
#   ..- attr(*, "percentMissing")= Named num 25.8
#   .. ..- attr(*, "names")= chr "copper"
#  $ CatTable : NULL
#  $ MetaData :List of 6
#   ..$ vars          : chr "copper"
#   ..$ logiFactors   : logi FALSE
#   ..$ varFactors    : chr(0) 
#   ..$ varNumerics   : chr "copper"
#   ..$ percentMissing: Named num 25.8
#   .. ..- attr(*, "names")= chr "copper"
#   ..$ varLabels     :List of 1
#   .. ..$ copper: NULL
#  - attr(*, "class")= chr [1:2] "svyTableOne" "TableOne"


### "Good" version
str(goodtab1weighted)
# List of 3
#  $ ContTable:List of 1
#   ..$ Overall: num [1, 1:10] 419.1 108.4 25.9 97.4 85.8 ...
#   .. ..- attr(*, "dimnames")=List of 2
#   .. .. ..$ : chr "copper"
#   .. .. ..$ : chr [1:10] "n" "miss" "p.miss" "mean" ...
#   ..- attr(*, "class")= chr [1:3] "svyContTable" "ContTable" "by"
#   ..- attr(*, "percentMissing")= Named num 25.8
#   .. ..- attr(*, "names")= chr "copper"
#  $ CatTable : NULL
#  $ MetaData :List of 6
#   ..$ vars          : chr "copper"
#   ..$ logiFactors   : logi FALSE
#   ..$ varFactors    : chr(0) 
#   ..$ varNumerics   : chr "copper"
#   ..$ percentMissing: Named num 25.8
#   .. ..- attr(*, "names")= chr "copper"
#   ..$ varLabels     :List of 1
#   .. ..$ copper: NULL
#  - attr(*, "class")= chr [1:2] "svyTableOne" "TableOne"

As you can see, the ContTable element differs in that it comprises a list of length 10 in the "bad" object and it comprises an array (matrix) in the "good" object. Combining this with the knowledge of exactly what R code (from the tableone:::print.svyContTable() method) produces the error, we can see that the code to print the objects tries (for each stratum) to extract the sample size and gets different results for the two input objects.

### "Bad" version
badtab1weighted$ContTable[[1]][, "n"]
# [[1]]
# [1] 419.0552

### "Good" version
goodtab1weighted$ContTable[[1]][, "n"]
# [1] 419.0552

The "bad" object produces a list of length 1 containing a vector of length 1 whereas the "good" object just produces the vector of length 1. The 2 numeric values are identical but the structures containing them are not. As a result, when you pass these results to the round() function, we see that the error described in this issue is generated when passing a list object to the round() function instead of passing just the vector contained in that list:

### "Bad" version
round(badtab1weighted$ContTable[[1]][, "n"])
# Error in round(badtab1weighted$ContTable[[1]][, "n"]) : 
#   non-numeric argument to mathematical function

### Fixed "bad" version (note the additional `[[1]]` at the end)
round(badtab1weighted$ContTable[[1]][, "n"][[1]])
# [1] 419

### "Good" version
round(goodtab1weighted$ContTable[[1]][, "n"])
# [1] 419

Sadly, that is where my investigation ends. I don't have enough knowledge to suggest a fix for this issue. Even worse, I have absolutely no idea why running the same code in 2 different R sessions both using the same (latest) versions of the tableone (v. 0.13.0) and survey (4.1-1) packages and the same data set generates an error in one and not the other.

Was it because of the different R versions (v. 4.0.3 vs v. 4.0.5)? Was it because the R sessions were running under different operating systems (Windows 10 (64-bit) vs Windows 2012 Server R2 (64-bit))? Was it because of something to do with R v. 4.0.3 running packages that were compiled under R v. 4.0.5? Was it because of the bug fix made to the round() function in R v. 4.0.4 (and presumably carried through to R v. 4.0.5)?

Hopefully a more knowledgeable person will be able to address this issue. And who knows? Maybe it's not even an issue in R v. 4.1.0 and later.

@docvock
Copy link

docvock commented Sep 27, 2021

Thanks for the very detailed explanations above. Extremely helpful. It appears that the survey package (v 4.1-1) changed the output for quantiles (object class "newsvyquantile"). The tableone package does not work with this new output format. The function oldsvyquantile gives the output in the older format. As an interim fix, the following lines of code should work

svyQuant_alt <- function (vars, design, q = 0.5) {
res <- vector()
for (i in 1:length(vars)) {
var <- vars[i]
res[i] <- oldsvyquantile(design$variables[var], design = design,
quantiles = q[1], na.rm = TRUE)
}
out <- as.vector(res)
names(out) <- vars
out
}

environment(svyQuant_alt) <- asNamespace('tableone')
assignInNamespace("svyQuant", svyQuant_alt, ns = "tableone")

@stjeandenise
Copy link
Author

thanks so much for the workaround, @docvock!!

@LaurenSamuels
Copy link

Thank you both for all of this!

@SMcClurePhD
Copy link

Thank you so much! I just ran into this issue today (while I was teaching a class with this code) and thought I had made some terrible error (other than not triple checking before classtime...).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants