BetaDistribution.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.statistics.distribution;

  18. import org.apache.commons.numbers.gamma.LogBeta;
  19. import org.apache.commons.numbers.gamma.RegularizedBeta;
  20. import org.apache.commons.rng.UniformRandomProvider;
  21. import org.apache.commons.rng.sampling.distribution.ChengBetaSampler;

  22. /**
  23.  * Implementation of the beta distribution.
  24.  *
  25.  * <p>The probability density function of \( X \) is:
  26.  *
  27.  * <p>\[ f(x; \alpha, \beta) = \frac{1}{ B(\alpha, \beta)} x^{\alpha-1} (1-x)^{\beta-1} \]
  28.  *
  29.  * <p>for \( \alpha &gt; 0 \),
  30.  * \( \beta &gt; 0 \), \( x \in [0, 1] \), and
  31.  * the beta function, \( B \), is a normalization constant:
  32.  *
  33.  * <p>\[ B(\alpha, \beta) = \frac{\Gamma(\alpha+\beta)}{\Gamma(\alpha) \Gamma(\beta)} \]
  34.  *
  35.  * <p>where \( \Gamma \) is the gamma function.
  36.  *
  37.  * <p>\( \alpha \) and \( \beta \) are <em>shape</em> parameters.
  38.  *
  39.  * @see <a href="https://en.wikipedia.org/wiki/Beta_distribution">Beta distribution (Wikipedia)</a>
  40.  * @see <a href="https://mathworld.wolfram.com/BetaDistribution.html">Beta distribution (MathWorld)</a>
  41.  */
  42. public final class BetaDistribution extends AbstractContinuousDistribution {
  43.     /** First shape parameter. */
  44.     private final double alpha;
  45.     /** Second shape parameter. */
  46.     private final double beta;
  47.     /** Normalizing factor used in log density computations. log(beta(a, b)). */
  48.     private final double logBeta;
  49.     /** Cached value for inverse probability function. */
  50.     private final double mean;
  51.     /** Cached value for inverse probability function. */
  52.     private final double variance;

  53.     /**
  54.      * @param alpha First shape parameter (must be positive).
  55.      * @param beta Second shape parameter (must be positive).
  56.      */
  57.     private BetaDistribution(double alpha,
  58.                              double beta) {
  59.         this.alpha = alpha;
  60.         this.beta = beta;
  61.         logBeta = LogBeta.value(alpha, beta);
  62.         final double alphabetasum = alpha + beta;
  63.         mean = alpha / alphabetasum;
  64.         variance = (alpha * beta) / ((alphabetasum * alphabetasum) * (alphabetasum + 1));
  65.     }

  66.     /**
  67.      * Creates a beta distribution.
  68.      *
  69.      * @param alpha First shape parameter (must be positive).
  70.      * @param beta Second shape parameter (must be positive).
  71.      * @return the distribution
  72.      * @throws IllegalArgumentException if {@code alpha <= 0} or {@code beta <= 0}.
  73.      */
  74.     public static BetaDistribution of(double alpha,
  75.                                       double beta) {
  76.         if (alpha <= 0) {
  77.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, alpha);
  78.         }
  79.         if (beta <= 0) {
  80.             throw new DistributionException(DistributionException.NOT_STRICTLY_POSITIVE, beta);
  81.         }
  82.         return new BetaDistribution(alpha, beta);
  83.     }

  84.     /**
  85.      * Gets the first shape parameter of this distribution.
  86.      *
  87.      * @return the first shape parameter.
  88.      */
  89.     public double getAlpha() {
  90.         return alpha;
  91.     }

  92.     /**
  93.      * Gets the second shape parameter of this distribution.
  94.      *
  95.      * @return the second shape parameter.
  96.      */
  97.     public double getBeta() {
  98.         return beta;
  99.     }

  100.     /** {@inheritDoc}
  101.      *
  102.      * <p>The density is not defined when {@code x = 0, alpha < 1}, or {@code x = 1, beta < 1}.
  103.      * In this case the limit of infinity is returned.
  104.      */
  105.     @Override
  106.     public double density(double x) {
  107.         if (x < 0 || x > 1) {
  108.             return 0;
  109.         }
  110.         return RegularizedBeta.derivative(x, alpha, beta);
  111.     }

  112.     /** {@inheritDoc}
  113.      *
  114.      * <p>The density is not defined when {@code x = 0, alpha < 1}, or {@code x = 1, beta < 1}.
  115.      * In this case the limit of infinity is returned.
  116.      */
  117.     @Override
  118.     public double logDensity(double x) {
  119.         if (x < 0 || x > 1) {
  120.             return Double.NEGATIVE_INFINITY;
  121.         } else if (x == 0) {
  122.             if (alpha < 1) {
  123.                 // Distribution is not valid when x=0, alpha<1
  124.                 // due to a divide by zero error.
  125.                 // Do not raise an exception and return the limit.
  126.                 return Double.POSITIVE_INFINITY;
  127.             }
  128.             // Special case of cancellation: x^(a-1) (1-x)^(b-1) / B(a, b) = 1 / B(a, b)
  129.             if (alpha == 1) {
  130.                 return -logBeta;
  131.             }
  132.             return Double.NEGATIVE_INFINITY;
  133.         } else if (x == 1) {
  134.             if (beta < 1) {
  135.                 // Distribution is not valid when x=1, beta<1
  136.                 // due to a divide by zero error.
  137.                 // Do not raise an exception and return the limit.
  138.                 return Double.POSITIVE_INFINITY;
  139.             }
  140.             // Special case of cancellation: x^(a-1) (1-x)^(b-1) / B(a, b) = 1 / B(a, b)
  141.             if (beta == 1) {
  142.                 return -logBeta;
  143.             }
  144.             return Double.NEGATIVE_INFINITY;
  145.         }

  146.         // Log computation
  147.         final double logX = Math.log(x);
  148.         final double log1mX = Math.log1p(-x);
  149.         return (alpha - 1) * logX + (beta - 1) * log1mX - logBeta;
  150.     }

  151.     /** {@inheritDoc} */
  152.     @Override
  153.     public double cumulativeProbability(double x)  {
  154.         if (x <= 0) {
  155.             return 0;
  156.         } else if (x >= 1) {
  157.             return 1;
  158.         } else {
  159.             return RegularizedBeta.value(x, alpha, beta);
  160.         }
  161.     }

  162.     /** {@inheritDoc} */
  163.     @Override
  164.     public double survivalProbability(double x) {
  165.         if (x <= 0) {
  166.             return 1;
  167.         } else if (x >= 1) {
  168.             return 0;
  169.         } else {
  170.             return RegularizedBeta.complement(x, alpha, beta);
  171.         }
  172.     }

  173.     /**
  174.      * {@inheritDoc}
  175.      *
  176.      * <p>For first shape parameter \( \alpha \) and second shape parameter
  177.      * \( \beta \), the mean is:
  178.      *
  179.      * <p>\[ \frac{\alpha}{\alpha + \beta} \]
  180.      */
  181.     @Override
  182.     public double getMean() {
  183.         return mean;
  184.     }

  185.     /**
  186.      * {@inheritDoc}
  187.      *
  188.      * <p>For first shape parameter \( \alpha \) and second shape parameter
  189.      * \( \beta \), the variance is:
  190.      *
  191.      * <p>\[ \frac{\alpha \beta}{(\alpha + \beta)^2 (\alpha + \beta + 1)} \]
  192.      */
  193.     @Override
  194.     public double getVariance() {
  195.         return variance;
  196.     }

  197.     /**
  198.      * {@inheritDoc}
  199.      *
  200.      * <p>The lower bound of the support is always 0.
  201.      *
  202.      * @return 0.
  203.      */
  204.     @Override
  205.     public double getSupportLowerBound() {
  206.         return 0;
  207.     }

  208.     /**
  209.      * {@inheritDoc}
  210.      *
  211.      * <p>The upper bound of the support is always 1.
  212.      *
  213.      * @return 1.
  214.      */
  215.     @Override
  216.     public double getSupportUpperBound() {
  217.         return 1;
  218.     }

  219.     /** {@inheritDoc} */
  220.     @Override
  221.     public ContinuousDistribution.Sampler createSampler(final UniformRandomProvider rng) {
  222.         // Beta distribution sampler.
  223.         return ChengBetaSampler.of(rng, alpha, beta)::sample;
  224.     }
  225. }