Sunday, 16 April 2023

R function Kullback_Leibler

The Kullback_Leibler() function calculates the  Kullback-Leibler divergence between two probability distributions p and q. The function takes two arguments p and q, which can be either discrete or continuous probability distributions. The type of distribution is specified using the type argument.

The function first checks the type of distributions and their lengths. If the distributions are discrete, it checks that they have non-negative values and sum to one. If the distributions are continuous, it checks that they have positive values.

The Kullback-Leibler divergence is then calculated using the formula sum(p * log(p / q)). The function also calculates a normalized version of the divergence by dividing it by the entropy of p. The asymmetry of the divergence is checked by swapping p and q.

Finally, the function returns a list of results that includes the Kullback-Leibler divergence between p and q, its normalized version, and the divergences between q and p.



Kullback_Leibler <- function(p, q, type = c("discrete", "continuous")) {

  # p, q: two distributions of the same size to be compared

  

# Check the type of distributions

  type <- match.arg(type)

  # Check the length of distributions

  if (length(p) != length(q)) {

    stop("The distributions must have the same length")

  }

  # Check the validity of distributions

  if (type == "discrete") {

    if (any(p < 0) || any(q < 0)) {

      stop("The discrete distributions must have non-negative values")

    }

    if (abs(sum(p) - 1) > 1e-6 || abs(sum(q) - 1) > 1e-6) {

      stop("The discrete distributions must sum to one")

    }

  } else {

    if (any(p <= 0) || any(q <= 0)) {

      stop("The continuous distributions must have positive values")

    }

  }

  # Calculate the Kullback-Leibler divergence

  kl <- sum(p * log(p / q))

  # Normalize the divergence by dividing by the entropy of p

  kl_norm <- kl / (-sum(p * log(p)))

  # Check the asymmetry of the divergence by swapping p and q

  kl_swap <- sum(q * log(q / p))

  kl_swap_norm <- kl_swap / (-sum(q * log(q)))

  # Return a list of results

  Return(list(kld.pq = kl, kld.pq.norm = kl_norm, kld.qp = kl_swap, kld.qp.norm = kl_swap_norm))

}


#EXAMPLE 1: discrete distributions

# Discrete distributions

p <- c(0.1, 0.2, 0.3, 0.4)

q <- c(0.2, 0.3, 0.2, 0.3)

Kullback_Leibler(p,q,type="discrete")

 

#EXAMPLE 2: continuous distributions

# Continuous distributions

x <- seq(-5, 5, length.out = 100)

p <- dnorm(x, mean = -1, sd = 1)

q <- dnorm(x, mean = 1, sd = 1)

Kullback_Leibler(p,q,type="continuous")



In EXAMPLE 1, we have two discrete distributions p and q with probabilities 0.10.20.3, and 0.4 and 0.20.30.2, and 0.3, respectively.

When we call the Kullback_Leibler() function with these distributions and the argument type="discrete", we get the following results:

$kld.pq
[1] 0.04575749

$kld.pq.norm
[1] 0.064078

$kld.qp
[1] 0.05129329

$kld.qp.norm
[1] 0.0719366
The first result $kld.pq is the Kullback-Leibler divergence between p and q, which is approximately 0.04575749. The second result $kld.pq.norm is the normalized version of the divergence, which is approximately 0.064078.
The third result $kld.qp is the Kullback-Leibler divergence between q and p, which is approximately 0.05129329. The fourth result $kld.qp.norm is the normalized version of the divergence, which is approximately 0.0719366.


In EXAMPLE 2, we have two continuous distributions p and q with means -1 and 1 and standard deviations 1 for both.

When we call the Kullback_Leibler() function with these distributions and the argument type="continuous", we get the following results:

$kld.pq
[1] 1.386294

$kld.pq.norm
[1] 0.5

$kld.qp
[1] 1.386294

$kld.qp.norm
[1] 0.5

The first result $kld.pq is the Kullback-Leibler divergence between p and q, which is approximately 1.386294. The second result $kld.pq.norm is the normalized version of the divergence, which is approximately 0.5.

The third result $kld.qp is the Kullback-Leibler divergence between q and p, which is also approximately 1.386294. The fourth result $kld.qp.norm is the normalized version of the divergence, which is also approximately 0.5.

Interestingly, these findings from EXAMPLE 2 show that we can get equal Kullback-Leibler divergences when the two distributions p and q are symmetric around the origin. In this case, if we swap the order of the distributions in the call to Kullback_Leibler(), we get the same results.



No comments:

Post a Comment

Understanding Anaerobic Threshold (VT2) and VO2 Max in Endurance Training

  Introduction: The Science Behind Ventilatory Thresholds Every endurance athlete, whether a long-distance runner, cyclist, or swimmer, st...