1

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.

Akhil Nair
  • 3,144
  • 1
  • 17
  • 32

1 Answers1

1

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.

Roland
  • 127,288
  • 10
  • 191
  • 288
  • I would never have guessed `c` would be the answer.. Thanks very much @Roland. On the last comment, I realised the print/multiplication bit isn't clear but left it as it didn't seem too important.. Originally when I tried this, I carried the exact figure as an attribute as well and displayed the attribute using print - but in that case I couldn't edit it in the expected manner. – Akhil Nair Nov 15 '15 at 14:28
  • I made a mistake in the original question and wrote `CJ(letter -> c("A", "B", "C"), number -> 1:4)` which results in `letter` existing in the global scope which means that the `dt[(subset), .N]/dt[, .N]` line worked. Without `letter` in the global scope I'm given the error "`letter not found`". Is there anyway to have something that works like this, after correcting my initial error? – Akhil Nair Nov 19 '15 at 10:19