[R] Why does debugging print() change output of function?

David Winsemius dwinsemius at comcast.net
Sun Sep 7 07:37:53 CEST 2014


On Sep 6, 2014, at 4:24 PM, William Dunlap wrote:

> In your first example I get an error:
>> mtest.data.frame(testdata, valid2=="N", valid3 > 1)
>  Error in mtest.data.frame(testdata, valid2 == "N", valid3 > 1) :
>    object 'valid2' not found
> I expect the error because list(...) ought to evaluate the ... arguments.

Thank you (and JWDougherty) for looking at this. I see that the difference lies in the fact that I have vectors in my workspace that were used  as preliminaries in constructing my test case that are being accessed by my logical expressions.

 group1 <- paste("Group", rep(LETTERS[1:7], sep=''))
  group2 <- c("UNC", "UNC", "SS", "LS", "LS", "SS", "UNC")
  valid1 <- c("Y", "N", NA, "N", "Y", "Y", "N")
 valid2 <- c("N", "N", "Y", "N", "N", "Y", "N")
  valid3 <- c(1.4, 1.2, NA, 0.7, 0.3, NA, 1.7)
  valid4 <- c(0.4, 0.3, 0.53, 0.66, 0.3, 0.3, 0.71)
 valid5 <- c(8.5, 11.2,NA, NA, 8.3, NA, 11.7)

I should have executed rm(list=ls()) and repeated my testing before posting, but you 
> Use substitute() to get the unevaluated ... arguments up front and
> don't use substitute() in the loop over the elements of test.
> 
> There are several ways to get the unevaluated ... arguments.  E.g.,
>  f0 <- function(x, ..., drop=FALSE) match.call(expand.dots=FALSE)$...

>  f1 <- function(x, ..., drop=FALSE) substitute(...())
>  f2 <- function(x, ..., drop=FALSE) as.list(substitute(list(...)))[-1]

These three version are somewhat confusing,  the second one in particular makes it appear that the ellipsis is a function, while the other ones make it appear that they are an expression pointing to a list.

> 
> Your function could be the following, where I also fixed a problem
> with parent.frame() being
> called in the wrong scope and improved,

Yes, I was worried about that.

> IMO, the names on the output data.frame.
> 
> m2 <- function (x, ..., drop = FALSE, verbose = FALSE)
> {
>    tests <- substitute(...())
>    nms <- names(tests) # fix up names, since data.frame makes ugly ones
>    if (is.null(nms)) {
>        names(tests) <- paste0("T", seq_along(tests))
>    }
>    else if (any(nms == "")) {
>        names(tests)[nms == ""] <- paste0("T", which(nms == ""))
>    }
>    if (verbose) {
>        print(tests)
>    }
>    r <- if (length(tests) == 0) {
>        stop("no 'tests'")
>    }
>    else {
>        enclos <- parent.frame() # evaluate parent.frame() outside of FUN()
>        data.frame(lapply(tests, FUN=function(e) {
>            r <- eval(e, x, enclos)
>            if (!is.logical(r)) {
>                stop("'tests' must be logical")
>            }
>            r & !is.na(r)
>        }))
>    }
>    r
> }
> 
> used as:
>> m2(testdata, group2=="UNC", Eleven.Two=valid5=="11.2")
>     T1 Eleven.Two
> 1  TRUE      FALSE
> 2  TRUE       TRUE
> 3 FALSE      FALSE
> 4 FALSE      FALSE
> 5 FALSE      FALSE
> 6 FALSE      FALSE
> 7  TRUE      FALSE
> Bill Dunlap
> TIBCO Software
> wdunlap tibco.com

Thank you again, Bill. 

-- 
David.

> 
> 
> On Sat, Sep 6, 2014 at 3:31 PM, David Winsemius <dwinsemius at comcast.net> wrote:
>> The goal:
>>   to create a function modeled after `subset` (notorious for its non-standard evaluation) that will take a series of logical tests as unqiuoted expressions to be evaluated in the framework of a dataframe environment and return a dataframe of logicals:
>> 
>> 
>> mtest.data.frame <-
>> function (x, ..., drop=FALSE)
>> { tests <- list(...); print(tests)
>>     r <- if (length(tests)==0)
>>         stop("no 'tests'")
>>     else { cbind.data.frame(
>>             lapply( tests, function(t){
>>                          e <- substitute(t)
>>                          r <- eval(e, x, parent.frame() )
>>                  if ( !is.logical(r) ) {
>>                      stop("'tests' must be logical") }
>>                     r & !is.na(r) } ) )
>>     }
>> }
>> #--------------
>> 
>> testdata <- structure(list(group1 = structure(1:7, .Label = c("Group A",
>> "Group B", "Group C", "Group D", "Group E", "Group F", "Group G"
>> ), class = "factor"), group2 = structure(c(3L, 3L, 2L, 1L, 1L,
>> 2L, 3L), .Label = c("LS", "SS", "UNC"), class = "factor"), valid1 = structure(c(2L,
>> 1L, NA, 1L, 2L, 2L, 1L), .Label = c("N", "Y"), class = "factor"),
>>    valid2 = structure(c(1L, 1L, 2L, 1L, 1L, 2L, 1L), .Label = c("N",
>>    "Y"), class = "factor"), valid3 = structure(c(4L, 3L, NA,
>>    2L, 1L, NA, 5L), .Label = c("0.3", "0.7", "1.2", "1.4", "1.7"
>>    ), class = "factor"), valid4 = structure(c(2L, 1L, 3L, 4L,
>>    1L, 1L, 5L), .Label = c("0.3", "0.4", "0.53", "0.66", "0.71"
>>    ), class = "factor"), valid5 = structure(c(4L, 1L, NA, NA,
>>    3L, NA, 2L), .Label = c("11.2", "11.7", "8.3", "8.5"), class = "factor")), .Names = c("group1",
>> "group2", "valid1", "valid2", "valid3", "valid4", "valid5"), row.names = c(NA,
>> -7L), class = "data.frame")
>> 
>> #######
>> 
>> 
>>> mtest.data.frame(testdata, valid2=="N", valid3 > 1)
>> [[1]]
>> [1] "tests are"
>> 
>> [[2]]
>> [1]  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE
>> 
>> [[3]]
>> [1]  TRUE  TRUE    NA FALSE FALSE    NA  TRUE
>> 
>> This actually seemed to be somewhat successful, but when ...
>> 
>> Now if I take out the `print()` call for 'tests', I get an different answer:
>> 
>>> mtest.data.frame <-
>> +  function (x, ..., drop=FALSE)
>> +  { tests <- list(...)
>> +      r <- if (length(tests)==0)
>> +          stop("no 'tests'")
>> +      else { cbind.data.frame(
>> +              lapply( tests, function(t){
>> +                           e <- substitute(t)
>> +                           r <- eval(e, x, parent.frame() )
>> +                   if ( !is.logical(r) ) {
>> +                       stop("'tests' must be logical") }
>> +                      r & !is.na(r) } ) )
>> +      }
>> +  }
>>> mtest.data.frame(testdata, valid2=="N", valid3 > 1)
>>>  # i.e. no answer
>> 
>> --

David Winsemius
Alameda, CA, USA



More information about the R-help mailing list