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 required
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()
#> 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 required
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