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 value support description #> 1: Binom1_prob 0.5 [0,1] Probability of Success #> 2: Binom1_qprob 0.5 [0,1] Probability of failure #> 3: Binom1_size 10 ℕ+ Number of trials #> 4: Binom2_prob 0.5 [0,1] Probability of Success #> 5: Binom2_qprob 0.5 [0,1] Probability of failure #> 6: Binom2_size 10 ℕ+ Number of trials #> 7: mix_weights uniform {uniform} ∪ [0,1] Mixture weights.

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 value support description #> 1: Binom1_prob 0.2 [0,1] Probability of Success #> 2: Binom1_qprob 0.8 [0,1] Probability of failure #> 3: Binom1_size 10 ℕ+ Number of trials #> 4: Binom2_prob 0.5 [0,1] Probability of Success #> 5: Binom2_qprob 0.5 [0,1] Probability of failure #> 6: Binom2_size 10 ℕ+ Number of trials #> 7: mix_weights uniform {uniform} ∪ [0,1] Mixture weights. M$wrappedModels("Binom1")$parameters() #> id value support description #> 1: prob 0.2 [0,1] Probability of Success #> 2: qprob 0.8 [0,1] Probability of failure #> 3: size 10 ℕ+ Number of trials

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:

- Name - New name after wrapping
- short_name - New short_name after wrapping
- distlist - The named list of distributions (even if just one)
- description - Optional, updated description after wrapping

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

- As multiple distributions are wrapped we first call
`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). - We add a private variable
`.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. - The type and support are updated to account for multiple distributions.
- All implemented wrappers inherit from
`DistributionWrapper`

and again`lock_objects = FALSE`

- Implementing wrappers is similar to implementing a SDistribution or Kernel but generally more flexible
- We can add private methods/variables to a wrapper, but if possible, not public methods/variables
- Wrappers automatically add methods for accessing internally models and correctly getting/setting parameters