Search code examples
rdata.tabler-s3

Print method with user classes


I'm experimenting with S3-class methods and generic functions, but I'm having an issue which I think highlights a misunderstanding in my thinking. Perhaps I'm getting confused with how printing works, or how storing values and attributes works internally?

I've tried to google around to no avail, possibly because I'm not too sure what I'm looking for.

Setup

library(data.table)

# trivial data
dt <- CJ(letter = c("A", "B", "C"), number = 1:4)

# -- generic functions
coverage <- function (x, ...) {
  UseMethod("coverage", x)
}

prettyprint <- function (x, ...) {
  UseMethod("prettyprint", x)
}

Class methods

# coverage method to find % of data.table satisfying an expr
coverage.data.table <- function(dt, subset, desc) {
  e <- parse(text = subset)  # parse condition to expression
  coverage <- dt[eval(e), .N]/dt[, .N]  # express coverage as a percent 
  class(coverage) <- c("coverage", class(coverage))  # set as 'coverage' class
  attributes(coverage)[["desc"]] <- desc  # carry description for printing
  coverage
}

# human readable data.table coverage
prettyprint.coverage <- function(coverage) {
  desc <- attributes(coverage)[["desc"]]
  paste0(round(coverage*100, 2), "% ", desc)
}

# normal printing
print.coverage <- function(coverage) {

  # unsure what to put in here such that I can use
  # this value with standard other operations such 
  # as multiplication

}

coverageB <- coverage(dt, "letter == \"B\"", "of data.table is in B")

> coverageB  # prints nothing as expected from empty function
> prettyprint(coverageB)
  [1] "33.33% of data.table is in B"

Printing coverageB without loading print.coverage gives

> coverageB
[1] 0.3333333
attr(,"class")
[1] "coverage" "numeric" 
attr(,"desc")
[1] "of data.table is in B"

where I'd like some way to print just the 0.3333333.

Help would be much appreciated. Thanks.

(As a side note, I'm sure that eval(parse(...)) statement is not the right way to do things. Any pointers there would be appreciated too.)

I also wasn't sure what to title this - if anyone has any more appropriate suggestion I'm happy to change it.


Solution

  • Here are two better possibilities, the first following your approach (which might be faster due to automatic indexing, but I haven't benchmarked):

    coverage.data.table <- function(dt, subset, desc) {
      coverage <- dt[eval(substitute(subset)), .N]/dt[, .N]  # express coverage as a percent 
      #coverage <- dt[, mean(eval(substitute(subset)))]  # express coverage as a percent 
      class(coverage) <- c("coverage", class(coverage))  # set as 'coverage' class
      attributes(coverage)[["desc"]] <- desc  # carry description for printing
      coverage
    }
    

    Then you call it like this:

    coverageB <- coverage(dt, letter == "B", "of data.table is in B")
    

    Here is a print method that uses c to remove all attributes (see its documentation):

    # normal printing
    print.coverage <- function(coverage) {
      print.default(c(coverage))
    } 
    
    coverageB
    #[1] 0.3333333
    prettyprint(coverageB)
    #[1] "33.33% of data.table is in B"
    

    However, I don't understand your comments regarding the print method. The print method is in no way connected to multiplication.