The previous tutorial introduced decorators in distr6. These are used to add numeric and more complex methods to the distribution interface. Wrappers are the next big distr6 feature and serve a similar purpose.

Just like decorators, wrappers are a design pattern commonly used in object-oriented programming. For people interested in these principles, we are specifically using the ‘Adapter’ pattern from Design Patterns (Gamma et al. 1994). Instead of simply adding methods, as decorators do, wrappers also slightly alter the interface of the object and usually result in a slightly different object being created. For example,

class(Normal$new()) #> [1] "Normal" "SDistribution" "Distribution" "R6" class(truncate(Normal$new())) #> [1] "TruncatedDistribution" "DistributionWrapper" "Distribution" #> [4] "R6"

See how the Normal distribution becomes an object of class `TruncatedDistribution`

which in itself is of class `DistributionWrapper`

. This tells the machine to expect a slightly different interface but it still recognises the original Normal distribution that is being wrapped.

All wrappers share a common method `wrappedModels()`

. More often than not this is used internally but it’s still useful if you want to remind yourself of the original wrapped model.

truncate(Normal$new(),-1,1)$wrappedModels() #> $Norm #> Norm(mean = 0, var = 1, sd = 1, prec = 1)

You can also specify the name of the internal model to access it,

truncate(Normal$new(),-1,1)$wrappedModels("Norm") #> Norm(mean = 0, var = 1, sd = 1, prec = 1)

A slightly quicker approach is to use the `description`

variable which is updated each time a distribution is wrapped

truncate(Normal$new(),-1,1)$description #> [1] "Normal Probability Distribution. Truncated between -1 and 1." huberize(truncate(Normal$new(),-3,3),-2,2)$description #> [1] "Normal Probability Distribution. Truncated between -3 and 3. Huberized between -2 and 2."

Or finally just look at the new distribution name

There are currently 8 wrappers in distr6, these can be listed with `listWrappers()`

. They can be sectioned into two groups: composite wrappers and transformer wrappers. Composite wrappers create composite distributions, i.e. distributions that use internal functions to combine multiple distributions. Transformation wrappers perform some form of transformation on the distribution, e.g. scaling the distribution to a given mean or variance. We will look at three of these in detail and hopefully understanding the others will follow intuitively.

Note: The Convolution and Scale wrappers are still in an experimental lifecycle, meaning that we are aware they may be sub-optimal or bugs may be present.

This is perhaps the simplest wrapper in distr6. It can be constructed in one of two ways:

N <- Normal$new() TruncatedDistribution$new(N, lower = -2, upper = 2) #> TruncNorm(Norm_mean = 0, Norm_var = 1,...,trunc_lower = -2, trunc_upper = 2) truncate(N, lower = -2, upper = 2) #> TruncNorm(Norm_mean = 0, Norm_var = 1,...,trunc_lower = -2, trunc_upper = 2)

In both cases, the result has to be saved to truncate the distribution as these are not methods in the Normal distribution class, i.e. to use the truncated normal distribution:

# This is a good example of when piping is clean and effective library(magrittr) tn <- Normal$new() %>% truncate(-2,2) class(tn) #> [1] "TruncatedDistribution" "DistributionWrapper" "Distribution" #> [4] "R6"

In the example above we are truncating the Normal distribution between the limits -2 and 2, this updates the distribution support accordingly and re-distributes the weights of the pdf and cdf

A product distribution is a composite distribution in which two or more independent distributions are combined into a single joint distribution. Details about the maths behind this can be found in `?ProductDistribution`

. For now we use this as an example of the composite distributions in distr6. Say we want the product distribution of the binomial and normal distributions,

N <- Normal$new(mean = 2, var = 1) B <- Binomial$new(size = 5, prob = 0.2) P <- ProductDistribution$new(list(N, B)) print(P) #> Norm X Binom P$description #> [1] "Product of: Norm, Binom" class(P) #> [1] "ProductDistribution" "VectorDistribution" "DistributionWrapper" #> [4] "Distribution" "R6"

Now we can treat `P`

like any other multivariate distribution

A mixture distribution is a weighted sum of component distributions. By default the weights are assumed to follow the uniform distribution, i.e. the probability of each distribution is identical, but this can be edited. Below we take the mixture of Binomial, Normal and Exponential distributions.

N <- Normal$new() B <- Binomial$new() E <- Exponential$new() M <- MixtureDistribution$new(list(N, B, E), weights = c(0.1,0.5,0.4)) print(M) #> Norm wX Binom wX Exp M$description #> [1] "Mixture of: Norm, Binom, Exp" class(M) #> [1] "MixtureDistribution" "VectorDistribution" "DistributionWrapper" #> [4] "Distribution" "R6" M$cdf(1:3) #> [1] 0.3423538 0.4709346 0.5658877

There are plenty other wrappers to explore, including vector distributions that combine distributions into one vector and array distributions that are a type of product distribution for k-dimensional distributions of a specific type. The tools learnt from this tutorial are all applicable to every other wrapper and we hope you find the rest intuitive to use. In the next and final tutorial we conclude with running through how to construct your own custom distribution.