2

First a little bit of context:

In my package summarytools, I've defined a print method for objects of classs "summarytools". I have also created a function view() that handles objects created using by() or lapply() in such a way that the output doesn't include the lines stating the group -- or the variable in the case of lapply(); summarytools displays its own headings containing that information, so there is some redundancy when using print. Also, the main headings are not repeated when using view().

Here's an example. Note that in this version (in development), I included a message advising the use of view():

> library(summarytools)
> (tmp <- with(tobacco, by(smoker, gender, freq)))
gender: F
For best results printing list objects with summarytools, use view(x, method = 'pander')
Frequencies   
tobacco$smoker     
Type: Factor    
Group: gender = M   

              Freq   % Valid   % Valid Cum.   % Total   % Total Cum.
----------- ------ --------- -------------- --------- --------------
        Yes    147     30.06          30.06     30.06          30.06
         No    342     69.94         100.00     69.94         100.00
       <NA>      0                               0.00         100.00
      Total    489    100.00         100.00    100.00         100.00
------------------------------------------------------------------ 
gender: M
Frequencies   
tobacco$smoker     
Type: Factor    
Group: gender = F   

              Freq   % Valid   % Valid Cum.   % Total   % Total Cum.
----------- ------ --------- -------------- --------- --------------
        Yes    143     29.24          29.24     29.24          29.24
         No    346     70.76         100.00     70.76         100.00
       <NA>      0                               0.00         100.00
      Total    489    100.00         100.00    100.00         100.00

And now using view():

> view(tmp, method = "pander")
Frequencies   
tobacco$smoker     
Type: Factor    
Group: gender = M   

              Freq   % Valid   % Valid Cum.   % Total   % Total Cum.
----------- ------ --------- -------------- --------- --------------
        Yes    147     30.06          30.06     30.06          30.06
         No    342     69.94         100.00     69.94         100.00
       <NA>      0                               0.00         100.00
      Total    489    100.00         100.00    100.00         100.00

Group: gender = F   

              Freq   % Valid   % Valid Cum.   % Total   % Total Cum.
----------- ------ --------- -------------- --------- --------------
        Yes    143     29.24          29.24     29.24          29.24
         No    346     70.76         100.00     70.76         100.00
       <NA>      0                               0.00         100.00
      Total    489    100.00         100.00    100.00         100.00

I've thought about ways through which the objects of class "by" would automatically be dispatched to view() instead of print(). If I add the class "summarytools" to those objects, the print() method could redirect the call to view(), making it simpler for users to get proper, optimal outputs.

The solutions I've thought of, so far, are the following:

  1. Adding a "by" argument to the functions so that I have full control on the created objects' proporties. I'm not fond of this solution, since 1) I try to rely on base R functions that people are familiar with rather than introducing new parameters, and 2) I'd still have a similar issue when objects are created with lapply().
  2. Redefining by() so that when it's called from one of summarytools' functions, it appends the desired class to the created objects. I've avoided this because I'm hesitant to redefine base functions. I'd rather not see messages to the effect that objects have been masked when the package is loaded.
  3. Defining a package-specific by(), such as by_st(); I could use basically the same code as by.default() and by.data.frame(), the only difference being that I'd add the "summarytools" class to the created objects. This is a sort of compromise that I'm considering.

My question is the following: could there be other, maybe better solutions I'm not seeing?

Dominic Comtois
  • 10,230
  • 1
  • 39
  • 61

1 Answers1

3

You could use S3 method for print.by to dispatch to your custom function:

old.print.by = print.by # save the original function so we can restore it later
print.by = summarytools::view # redefine print.by to dispatch to custom function
tmp

enter image description here

To restore original function later you can do print.by = old.print.by.

If you only want your new function to operate on lists that contain objects of class "summarytools", you can use

print.by = function(x, method = 'pander', ...) {
  if ("summarytools" %in% class(x[[1]])) {
    summarytools::view(x, method, ...)
  } else {
    old.print.by(x, ...)
  }
}
dww
  • 30,425
  • 5
  • 68
  • 111
  • This seems very promising... So what you're saying is I could define and register `print.by()` in the package, and inside the function, check whether objects in the list are of class "summarytools", and if not, redispatch to the original `print.by()`? – Dominic Comtois Dec 27 '18 at 01:11
  • exactly, I edited the answer to show how you can dispatch to the desired function, depending on class of object. I use an `if` test for this, rather than S3, because the class of "summarytools" is buried within the objects in the list returned by `by`, rather than being a class of the top level list itself – dww Dec 27 '18 at 02:26
  • Very nice. I just replaced the call to `old.print.by` by a call to `base::print.by`, and it works like a charm. Many thanks to you, I wasn't convinced at all that I'd find a good solution like this! – Dominic Comtois Dec 27 '18 at 02:51