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:
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.
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.
DistributionDecorator
.isCdf, .isPdf,
.isQuantile, .isRand methods to exploit
analytical results.