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()
#> Id Support Value Tags
#> <char> <char> <list> <list>
#> 1: Binom1__prob [0,1] 0.5 Binom1__linked,required
#> 2: Binom1__qprob [0,1] Binom1__linked,required
#> 3: Binom1__size ℕ+ 10 required
#> 4: Binom2__prob [0,1] 0.5 Binom2__linked,required
#> 5: Binom2__qprob [0,1] Binom2__linked,required
#> 6: Binom2__size ℕ+ 10 required
#> 7: mix__weights ([0,1]^2) ∪ {uniform} uniform requiredAs 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()
#> Id Support Value Tags
#> <char> <char> <list> <list>
#> 1: Binom1__prob [0,1] 0.2 Binom1__linked,required
#> 2: Binom1__qprob [0,1] Binom1__linked,required
#> 3: Binom1__size ℕ+ 10 required
#> 4: Binom2__prob [0,1] 0.5 Binom2__linked,required
#> 5: Binom2__qprob [0,1] Binom2__linked,required
#> 6: Binom2__size ℕ+ 10 required
#> 7: mix__weights ([0,1]^2) ∪ {uniform} uniform required
M$wrappedModels("Binom1")$parameters()
#> Id Support Value Tags
#> <char> <char> <list> <list>
#> 1: prob [0,1] 0.5 linked,required
#> 2: qprob [0,1] linked,required
#> 3: size ℕ+ 10 requiredNow 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