library(distr6) set.seed(42) knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
Wrappers may the hardest class to extend in distr6, simply due to the fact that there are no set rules for what can and can't go in a wrapper. Every implemented wrapper inherits from the DistributionWrapper
class which in turn inherits from Distribution
(see the uml diagram). The DistributionWrapper
class introduces its own constructor, the wrappedModels
method and overloads the setParameterValue
method. We will briefly discuss why these three methods are important to all implemented methods.
The DistributionWrapper
constructor takes one named argument and has ...
for named arguments to passed to the Distribution
constructor. The named argument is distlist
, a list of distributions to wrap. Internally the constructor combines all ParameterSet
from every distribution into one ParameterSet
that the user can see and query.
For example, say we use the MixtureDistribution
wrapper on two Binomial distributions, see how the parameter names are automatically updated
M <- MixtureDistribution$new(list(Binomial$new(),Binomial$new())) M$parameters()
As the constructor may change the names of parameters, the setParameterValue
is overloaded to ensure the correct ParameterSet
from the corresponding distribution is updated. So given the example above, if a user wants to update the first Binomial distribution
M$setParameterValue(Binom1__prob = 0.2)
And internally the parameter name is split at the underscore and updates the parameter of the first Binomial distribution, notice now that both the external representation of the combined ParameterSet
is updated as well as the one in the internal model
M$parameters() M$wrappedModels("Binom1")$parameters()
We saw above the use of wrappedModels
, this is a simple accessor found in all implemented wrappers that ensure the underlying models can be accessed, which is usually required for the new methods in wrappers, which we will see more below.
Now we have a background of the DistributionWrapper
class we can discuss actually creating a wrapper. As discussed in the wrappers tutorial, there are multiple types of wrappers, each will be implemented slightly differently. Therefore we're just going to look at one quick example from each example and point you to the source code for further examples.
An implemented wrapper should consist of two parts: the wrapper definition and the wrapper constructor. In the case of the TruncatedDistribution
, a slightly abridged version looks like
TruncatedDistribution <- R6::R6Class("TruncatedDistribution", inherit = DistributionWrapper, lock_objects = FALSE) TruncatedDistribution$set("public","initialize",function(distribution, lower = NULL, upper = NULL){ pdf <- function(x1,...) { self$wrappedModels()[[1]]$pdf(x1) / (self$wrappedModels()[[1]]$cdf(self$sup()) - self$wrappedModels()[[1]]$cdf(self$inf())) } cdf <- function(x1,...){ num = self$wrappedModels()[[1]]$cdf(x1) - self$wrappedModels()[[1]]$cdf(self$inf()) den = self$wrappedModels()[[1]]$cdf(self$sup()) - self$wrappedModels()[[1]]$cdf(self$inf()) return(num/den) } name = paste("Truncated",distribution$name) short_name = paste0("Truncated",distribution$short_name) distlist = list(distribution) names(distlist) = distribution$short_name description = paste0(distribution$description, " Truncated between ",lower," and ",upper,".") super$initialize(distlist = distlist, pdf = pdf, cdf = cdf, name = name, short_name = short_name, support = support, type = distribution$type(), description = description) })
All wrappers should pass the following to the parent-class constructor:
And then any other arguments that may be changed by wrapping, remember that any arguments passed to the DistributionWrapper
constructor are in turn passed to the Distribution
constructor, hence we can think of implemented wrappers as custom distributions. Read the custom distribution tutorial for more information about the arguments passed to the Distribution
constructor.
Notice also that the new pdf
and cdf
methods reference the original model using the wrappedModels
method. This will generally be the case with all wrappers.
The MixtureDistribution
wrapper is slightly different in that it takes a composition of multiple distributions and it adds a private variable
MixtureDistribution <- R6::R6Class("MixtureDistribution", inherit = DistributionWrapper, lock_objects = FALSE) MixtureDistribution$set("public","initialize",function(distlist, weights = NULL){ distlist = makeUniqueDistributions(distlist) distnames = names(distlist) private$.weights <- weights pdf <- function(x1,...) { if(length(x1)==1) return(as.numeric(sum(sapply(self$wrappedModels(), function(y) y$pdf(x1)) * private$.weights))) else return(as.numeric(rowSums(sapply(self$wrappedModels(), function(y) y$pdf(x1)) %*% diag(private$.weights)))) } name = paste("Mixture of",paste(distnames, collapse = "_")) short_name = paste0("Mix_",paste(distnames, collapse = "_")) type = do.call(setunion, lapply(distlist, type)) support = do.call(setunion, lapply(distlist, type)) super$initialize(distlist = distlist, pdf = pdf, cdf = cdf, rand = rand, name = name, short_name = short_name, description = description, type = type, support = support, valueSupport = "mixture") }) MixtureDistribution$set("private",".weights",numeric(0))
Again this is a shortened version of the code. Note the following in the above
makeUniqueDistributions
, a helper function that ensures the IDs of the distributions are unique. This automatically clones all distributions passed to it to prevent the R6 copying problem (see here)..weights
which is accessed in the pdf/cdf. In general we allow additional private variables and methods to be added to wrappers, but not public ones.DistributionWrapper
and again lock_objects = FALSE
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.