library(distr6)
set.seed(42)
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE)

DistributionDecorator Class

Implementing your own decorator is the easiest extension you can make to distr6, the only difficulty arises in determining what should go in your decorator. If you plan on making a pull-request with a new decorator to distr6 then we would expect the following:

  1. A description of your decorator including what problem it solves and why it is required
  2. A description of methodology including the choice of numerical methods and a description of any packages depended on including how their code is written (e.g. have you seen their code, is it in R or C, are there references).
  3. A decorator that adds at least three methods. We do not want a clutter of decorators in distr6.

The DistributionDecorator class is an abstract class that doesn't add any methods or variables, its sole purpose is to define decorators in an abstract sense as a way to collate them. Decorators could function as named lists, for example the CoreStatistics decorator could instead be a list called 'CoreStatistics' with each named element being one of the added methods. However lists provide slightly less flexibility and would need to be created as the package is loaded, as opposed to a relatively small class that sits in the package namespace.

Creating a decorator

A decorator is just a class, inheriting from DistributionDecorator, with public methods that are added to a distribution when decorated. For example, the CoreStatistics decorator has a definition:

CoreStatistics <- R6::R6Class("CoreStatistics", inherit = DistributionDecorator)

and methods (below are just two, again shortened)

CoreStatistics$set("public", "mgf", function(t) {
  return(self$genExp(trafo = function(x) {return(exp(x*t))}))
})

CoreStatistics$set("public","genExp",function(trafo = NULL){
  if(is.null(trafo)){
    trafo = function() return(x)
    formals(trafo) = alist(x = )
  }
    message(.distr6$message_numeric)
    return(suppressMessages(integrate(function(x) {
      pdfs = self$pdf(x)
      xs = trafo(x)
      xs[pdfs==0] = 0
      return(xs * pdfs)
    }, lower = self$inf(), upper = self$sup())$value))
})

We don't have too many requirements in defining decorators apart from insisting on using the global '.distr6$message_numeric' string as a message to tell users that numeric results may not be exact. We use messages instead of warnings as warnings stack, which quickly becomes annoying.

Finally, we just want to point you in the direction of useful Distribution private variables that are rarely used outside of decorators, as an example take the survival method from the ExoticStatistics class

ExoticStatistics$set("public", "survival", function(x1, log.p = FALSE) {
  if(private$.isCdf)
    self$cdf(x1 = x1, lower.tail = FALSE, log.p = log.p)
  else {
    message(.distr6$message_numeric)
    surv = integrate(self$pdf, x1, self$sup())$value
    if(log.p)
      return(log(surv))
    else
      return(surv)
  }
})

Note the use of private$.isCdf, this checks to see if the cdf method is defined in the Distribution. Remember that every Distribution object always have public d/p/q/r methods defined but do not necessarily have a private d/p/q/r method which actually does all the work. Therefore every Distribution includes flags for whether or not the d/p/q/r method is defined. To test this yourself, create a custom distribution object with a pdf only, and see what happens when you call the cdf method (it will return NULL).

Therefore the use of private$.isCdf tells the decorator whether it can use analytical results or if numeric integration is required.

Note: if the user has already decorated their distribution with FunctionImputation then even if your decorator thinks the results are analytical, they are not. However this is taken into account by the printed message in the imputed d/p/q/r methods.

Summary

  1. Decorators are simple to implement, just define a new class inheriting from DistributionDecorator
  2. Add as many public methods as you want, but remember to include messages for numerical integration
  3. Make use of the private .isCdf, .isPdf, .isQuantile, .isRand methods to exploit analytical results.

Extension Guidelines



RaphaelS1/distr6 documentation built on Feb. 24, 2024, 9:14 p.m.